summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt44
-rw-r--r--api/system-current.txt44
-rw-r--r--core/java/android/app/ApplicationPackageManager.java57
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java361
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java165
-rw-r--r--core/java/android/bluetooth/BluetoothProfile.java10
-rw-r--r--core/java/android/bluetooth/BluetoothSap.java383
-rw-r--r--core/java/android/bluetooth/BluetoothServerSocket.java52
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java197
-rw-r--r--core/java/android/bluetooth/BluetoothUuid.java9
-rw-r--r--core/java/android/bluetooth/IBluetooth.aidl6
-rw-r--r--core/java/android/bluetooth/IBluetoothGatt.aidl2
-rw-r--r--core/java/android/bluetooth/IBluetoothManager.aidl2
-rw-r--r--core/java/android/bluetooth/IBluetoothManagerCallback.aidl3
-rw-r--r--core/java/android/bluetooth/IBluetoothSap.aidl37
-rw-r--r--core/java/android/bluetooth/SdpMasRecord.java147
-rw-r--r--core/java/android/bluetooth/SdpMnsRecord.java112
-rw-r--r--core/java/android/bluetooth/SdpOppOpsRecord.java118
-rw-r--r--core/java/android/bluetooth/SdpPseRecord.java125
-rw-r--r--core/java/android/bluetooth/SdpRecord.java76
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeScanner.java4
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeUtils.java2
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java12
-rw-r--r--core/java/android/content/pm/PackageManager.java7
-rw-r--r--core/java/android/net/ConnectivityManager.java119
-rw-r--r--core/java/android/net/IConnectivityManager.aidl9
-rw-r--r--core/java/android/net/NetworkAgent.java30
-rw-r--r--core/java/android/os/storage/DiskInfo.java15
-rw-r--r--core/java/android/os/storage/VolumeInfo.java36
-rw-r--r--core/java/android/preference/SeekBarVolumizer.java11
-rw-r--r--core/java/android/provider/Settings.java12
-rw-r--r--core/java/android/text/StaticLayout.java15
-rw-r--r--core/java/android/view/DisplayListCanvas.java6
-rw-r--r--core/java/android/view/GestureDetector.java88
-rw-r--r--core/java/android/webkit/WebViewFactory.java71
-rw-r--r--core/java/android/widget/AbsListView.java28
-rw-r--r--core/java/android/widget/Editor.java36
-rw-r--r--core/java/android/widget/TextView.java79
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java79
-rw-r--r--core/jni/android/graphics/NinePatch.cpp45
-rw-r--r--core/jni/android_graphics_Canvas.cpp42
-rw-r--r--core/jni/android_text_StaticLayout.cpp8
-rw-r--r--core/jni/android_view_DisplayListCanvas.cpp8
-rw-r--r--core/res/res/drawable/ic_audio_media.xml27
-rw-r--r--core/res/res/drawable/ic_audio_media_mute.xml30
-rw-r--r--core/res/res/layout/floating_popup_container.xml4
-rw-r--r--core/res/res/layout/floating_popup_menu_button.xml8
-rw-r--r--core/res/res/layout/floating_popup_overflow_list_item6
-rw-r--r--core/res/res/values/attrs.xml4
-rw-r--r--core/res/res/values/colors.xml4
-rwxr-xr-xcore/res/res/values/config.xml6
-rw-r--r--core/res/res/values/dimens.xml12
-rw-r--r--core/res/res/values/public.xml3
-rwxr-xr-xcore/res/res/values/symbols.xml9
-rw-r--r--graphics/java/android/graphics/Bitmap.java5
-rw-r--r--graphics/java/android/graphics/Canvas.java18
-rw-r--r--graphics/java/android/graphics/NinePatch.java16
-rw-r--r--libs/hwui/DisplayListRenderer.cpp14
-rw-r--r--libs/hwui/DisplayListRenderer.h4
-rw-r--r--libs/hwui/ResourceCache.cpp6
-rw-r--r--libs/hwui/ResourceCache.h10
-rw-r--r--media/jni/android_media_MediaSync.cpp22
-rw-r--r--media/jni/android_media_MediaSync.h2
-rw-r--r--obex/Android.mk11
-rw-r--r--obex/javax/obex/ClientOperation.java174
-rw-r--r--obex/javax/obex/ClientSession.java247
-rw-r--r--obex/javax/obex/HeaderSet.java65
-rw-r--r--obex/javax/obex/ObexHelper.java87
-rw-r--r--obex/javax/obex/ObexPacket.java71
-rw-r--r--obex/javax/obex/ObexSession.java6
-rw-r--r--obex/javax/obex/ObexTransport.java35
-rw-r--r--obex/javax/obex/ServerOperation.java416
-rw-r--r--obex/javax/obex/ServerRequestHandler.java9
-rw-r--r--obex/javax/obex/ServerSession.java59
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/CopyService.java3
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/PickFragment.java5
-rw-r--r--packages/SystemUI/res/anim/ic_qs_signal_blink_1.xml38
-rw-r--r--packages/SystemUI/res/anim/ic_qs_signal_blink_2.xml44
-rw-r--r--packages/SystemUI/res/anim/ic_qs_signal_blink_3.xml38
-rw-r--r--packages/SystemUI/res/anim/ic_signal_blink_1.xml38
-rw-r--r--packages/SystemUI/res/anim/ic_signal_blink_2.xml44
-rw-r--r--packages/SystemUI/res/anim/ic_signal_blink_3.xml38
-rw-r--r--packages/SystemUI/res/anim/ic_signal_dark_blink_1.xml38
-rw-r--r--packages/SystemUI/res/anim/ic_signal_dark_blink_2.xml44
-rw-r--r--packages/SystemUI/res/anim/ic_signal_dark_blink_3.xml38
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml36
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change_animation.xml27
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change.xml36
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change_animation.xml27
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_signal_dark_carrier_network_change_animation.xml27
-rw-r--r--packages/SystemUI/res/values-af/strings.xml1
-rw-r--r--packages/SystemUI/res/values-am/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml1
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml1
-rw-r--r--packages/SystemUI/res/values-bn-rBD/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml1
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml1
-rw-r--r--packages/SystemUI/res/values-da/strings.xml1
-rw-r--r--packages/SystemUI/res/values-de/strings.xml1
-rw-r--r--packages/SystemUI/res/values-el/strings.xml1
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml22
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml22
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml22
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml1
-rw-r--r--packages/SystemUI/res/values-es/strings.xml1
-rw-r--r--packages/SystemUI/res/values-et-rEE/strings.xml1
-rw-r--r--packages/SystemUI/res/values-eu-rES/strings.xml1
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml1
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml1
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml1
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml1
-rw-r--r--packages/SystemUI/res/values-gl-rES/strings.xml1
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml1
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml1
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml1
-rw-r--r--packages/SystemUI/res/values-hy-rAM/strings.xml1
-rw-r--r--packages/SystemUI/res/values-in/strings.xml1
-rw-r--r--packages/SystemUI/res/values-is-rIS/strings.xml1
-rw-r--r--packages/SystemUI/res/values-it/strings.xml1
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ka-rGE/strings.xml1
-rw-r--r--packages/SystemUI/res/values-kk-rKZ/strings.xml1
-rw-r--r--packages/SystemUI/res/values-km-rKH/strings.xml1
-rw-r--r--packages/SystemUI/res/values-kn-rIN/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ky-rKG/strings.xml1
-rw-r--r--packages/SystemUI/res/values-lo-rLA/strings.xml1
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml1
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml1
-rw-r--r--packages/SystemUI/res/values-mk-rMK/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ml-rIN/strings.xml1
-rw-r--r--packages/SystemUI/res/values-mn-rMN/strings.xml1
-rw-r--r--packages/SystemUI/res/values-mr-rIN/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ms-rMY/strings.xml1
-rw-r--r--packages/SystemUI/res/values-my-rMM/strings.xml1
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ne-rNP/strings.xml1
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml1
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml1
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml1
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml1
-rw-r--r--packages/SystemUI/res/values-si-rLK/strings.xml22
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml1
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml1
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml1
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml1
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ta-rIN/strings.xml1
-rw-r--r--packages/SystemUI/res/values-te-rIN/strings.xml1
-rw-r--r--packages/SystemUI/res/values-th/strings.xml1
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml1
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml1
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml1
-rw-r--r--packages/SystemUI/res/values-ur-rPK/strings.xml1
-rw-r--r--packages/SystemUI/res/values-uz-rUZ/strings.xml1
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml1
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml1
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml1
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml1
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml1
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTile.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java2
-rw-r--r--rs/java/android/renderscript/Allocation.java2
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java283
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java143
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java66
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkAgentInfo.java9
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkMonitor.java2
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java29
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java3
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateService.java21
-rw-r--r--telecomm/java/android/telecom/Call.java16
-rw-r--r--telecomm/java/android/telecom/Connection.java14
-rw-r--r--telephony/java/android/telephony/PhoneStateListener.java32
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java29
-rw-r--r--telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl1
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl1
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java17
-rw-r--r--test-runner/src/android/test/mock/MockPackageManager.java14
-rw-r--r--tools/aapt2/Android.mk6
-rw-r--r--tools/aapt2/BinaryResourceParser.cpp67
-rw-r--r--tools/aapt2/BinaryResourceParser.h7
-rw-r--r--tools/aapt2/BinaryXmlPullParser.cpp204
-rw-r--r--tools/aapt2/BinaryXmlPullParser.h73
-rw-r--r--tools/aapt2/Flag.cpp16
-rw-r--r--tools/aapt2/Flag.h5
-rw-r--r--tools/aapt2/JavaClassGenerator.cpp126
-rw-r--r--tools/aapt2/JavaClassGenerator.h13
-rw-r--r--tools/aapt2/JavaClassGenerator_test.cpp35
-rw-r--r--tools/aapt2/Linker.cpp16
-rw-r--r--tools/aapt2/Linker_test.cpp17
-rw-r--r--tools/aapt2/Main.cpp868
-rw-r--r--tools/aapt2/NameMangler.h54
-rw-r--r--tools/aapt2/NameMangler_test.cpp45
-rw-r--r--tools/aapt2/Png.cpp18
-rw-r--r--tools/aapt2/Png.h3
-rw-r--r--tools/aapt2/ResChunkPullParser.h16
-rw-r--r--tools/aapt2/Resolver.cpp36
-rw-r--r--tools/aapt2/Resolver.h2
-rw-r--r--tools/aapt2/Resource.h14
-rw-r--r--tools/aapt2/ResourceParser.cpp130
-rw-r--r--tools/aapt2/ResourceParser.h13
-rw-r--r--tools/aapt2/ResourceParser_test.cpp38
-rw-r--r--tools/aapt2/ResourceTable.cpp66
-rw-r--r--tools/aapt2/ResourceTable.h6
-rw-r--r--tools/aapt2/ResourceTable_test.cpp4
-rw-r--r--tools/aapt2/ResourceValues.cpp49
-rw-r--r--tools/aapt2/ResourceValues.h37
-rw-r--r--tools/aapt2/StringPool.cpp21
-rw-r--r--tools/aapt2/StringPool.h6
-rw-r--r--tools/aapt2/StringPool_test.cpp23
-rw-r--r--tools/aapt2/XmlFlattener.cpp66
-rw-r--r--tools/aapt2/XmlFlattener.h4
-rw-r--r--tools/aapt2/XmlFlattener_test.cpp2
-rw-r--r--tools/aapt2/ZipEntry.cpp739
-rw-r--r--tools/aapt2/ZipEntry.h349
-rw-r--r--tools/aapt2/ZipFile.cpp1305
-rw-r--r--tools/aapt2/ZipFile.h276
-rw-r--r--tools/aapt2/data/Makefile56
-rw-r--r--tools/aapt2/data/lib/AndroidManifest.xml3
-rw-r--r--tools/aapt2/data/lib/Makefile81
-rw-r--r--tools/aapt2/data/lib/res/values/styles.xml6
-rw-r--r--tools/aapt2/data/res/values/styles.xml2
-rw-r--r--tools/aapt2/process.dot18
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java131
237 files changed, 9157 insertions, 1589 deletions
diff --git a/Android.mk b/Android.mk
index 7ec010d34db0..122347536058 100644
--- a/Android.mk
+++ b/Android.mk
@@ -113,6 +113,7 @@ LOCAL_SRC_FILES += \
core/java/android/bluetooth/IBluetoothManagerCallback.aidl \
core/java/android/bluetooth/IBluetoothPbap.aidl \
core/java/android/bluetooth/IBluetoothMap.aidl \
+ core/java/android/bluetooth/IBluetoothSap.aidl \
core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \
core/java/android/bluetooth/IBluetoothHeadsetClient.aidl \
core/java/android/bluetooth/IBluetoothGatt.aidl \
diff --git a/api/current.txt b/api/current.txt
index 66931812b8b9..4f2efb2c47e4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -793,6 +793,7 @@ package android {
field public static final int layout_x = 16843135; // 0x101017f
field public static final int layout_y = 16843136; // 0x1010180
field public static final int left = 16843181; // 0x10101ad
+ field public static final int leftIndents = 16844016; // 0x10104f0
field public static final int letterSpacing = 16843958; // 0x10104b6
field public static final int lineSpacingExtra = 16843287; // 0x1010217
field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -1021,6 +1022,7 @@ package android {
field public static final int reversible = 16843851; // 0x101044b
field public static final int revisionCode = 16843989; // 0x10104d5
field public static final int right = 16843183; // 0x10101af
+ field public static final int rightIndents = 16844017; // 0x10104f1
field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
field public static final int ringtoneType = 16843257; // 0x10101f9
field public static final int rotation = 16843558; // 0x1010326
@@ -6949,6 +6951,7 @@ package android.bluetooth {
field public static final int GATT_SERVER = 8; // 0x8
field public static final int HEADSET = 1; // 0x1
field public static final int HEALTH = 3; // 0x3
+ field public static final int SAP = 10; // 0xa
field public static final int STATE_CONNECTED = 2; // 0x2
field public static final int STATE_CONNECTING = 1; // 0x1
field public static final int STATE_DISCONNECTED = 0; // 0x0
@@ -6960,6 +6963,25 @@ package android.bluetooth {
method public abstract void onServiceDisconnected(int);
}
+ public final class BluetoothSap implements android.bluetooth.BluetoothProfile {
+ method public synchronized void close();
+ method public boolean connect(android.bluetooth.BluetoothDevice);
+ method public boolean disconnect(android.bluetooth.BluetoothDevice);
+ method public android.bluetooth.BluetoothDevice getClient();
+ method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ method public int getPriority(android.bluetooth.BluetoothDevice);
+ method public int getState();
+ method public boolean isConnected(android.bluetooth.BluetoothDevice);
+ method public boolean setPriority(android.bluetooth.BluetoothDevice, int);
+ field public static final java.lang.String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED";
+ field public static final int RESULT_CANCELED = 2; // 0x2
+ field public static final int RESULT_FAILURE = 0; // 0x0
+ field public static final int RESULT_SUCCESS = 1; // 0x1
+ field public static final int STATE_ERROR = -1; // 0xffffffff
+ }
+
public final class BluetoothServerSocket implements java.io.Closeable {
method public android.bluetooth.BluetoothSocket accept() throws java.io.IOException;
method public android.bluetooth.BluetoothSocket accept(int) throws java.io.IOException;
@@ -6969,10 +6991,16 @@ package android.bluetooth {
public final class BluetoothSocket implements java.io.Closeable {
method public void close() throws java.io.IOException;
method public void connect() throws java.io.IOException;
+ method public int getConnectionType();
method public java.io.InputStream getInputStream() throws java.io.IOException;
+ method public int getMaxReceivePacketSize();
+ method public int getMaxTransmitPacketSize();
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
method public android.bluetooth.BluetoothDevice getRemoteDevice();
method public boolean isConnected();
+ field public static final int TYPE_L2CAP = 3; // 0x3
+ field public static final int TYPE_RFCOMM = 1; // 0x1
+ field public static final int TYPE_SCO = 2; // 0x2
}
}
@@ -18106,6 +18134,7 @@ package android.net {
public class ConnectivityManager {
method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
method public boolean bindProcessToNetwork(android.net.Network);
+ method public android.net.Network getActiveNetwork();
method public android.net.NetworkInfo getActiveNetworkInfo();
method public android.net.NetworkInfo[] getAllNetworkInfo();
method public android.net.Network[] getAllNetworks();
@@ -18124,7 +18153,8 @@ package android.net {
method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
method public void releaseNetworkRequest(android.app.PendingIntent);
method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
- method public void reportBadNetwork(android.net.Network);
+ method public deprecated void reportBadNetwork(android.net.Network);
+ method public void reportNetworkConnectivity(android.net.Network, boolean);
method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
method public deprecated boolean requestRouteToHost(int, int);
@@ -30940,6 +30970,7 @@ package android.telephony {
method public boolean isVoiceCapable();
method public boolean isWorldPhone();
method public void listen(android.telephony.PhoneStateListener, int);
+ method public void notifyCarrierNetworkChange(boolean);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
@@ -34437,6 +34468,7 @@ package android.view {
method public boolean onTouchEvent(android.view.MotionEvent);
method public void setIsLongpressEnabled(boolean);
method public void setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener);
+ method public void setOnStylusButtonPressListener(android.view.GestureDetector.OnStylusButtonPressListener);
}
public static abstract interface GestureDetector.OnDoubleTapListener {
@@ -34454,7 +34486,11 @@ package android.view {
method public abstract boolean onSingleTapUp(android.view.MotionEvent);
}
- public static class GestureDetector.SimpleOnGestureListener implements android.view.GestureDetector.OnDoubleTapListener android.view.GestureDetector.OnGestureListener {
+ public static abstract interface GestureDetector.OnStylusButtonPressListener {
+ method public abstract boolean onStylusButtonPress(android.view.MotionEvent);
+ }
+
+ public static class GestureDetector.SimpleOnGestureListener implements android.view.GestureDetector.OnDoubleTapListener android.view.GestureDetector.OnGestureListener android.view.GestureDetector.OnStylusButtonPressListener {
ctor public GestureDetector.SimpleOnGestureListener();
method public boolean onDoubleTap(android.view.MotionEvent);
method public boolean onDoubleTapEvent(android.view.MotionEvent);
@@ -34465,6 +34501,7 @@ package android.view {
method public void onShowPress(android.view.MotionEvent);
method public boolean onSingleTapConfirmed(android.view.MotionEvent);
method public boolean onSingleTapUp(android.view.MotionEvent);
+ method public boolean onStylusButtonPress(android.view.MotionEvent);
}
public class Gravity {
@@ -41225,6 +41262,7 @@ package android.widget {
method public int getInputType();
method public final android.text.method.KeyListener getKeyListener();
method public final android.text.Layout getLayout();
+ method public int[] getLeftIndents();
method public float getLetterSpacing();
method public int getLineBounds(int, android.graphics.Rect);
method public int getLineCount();
@@ -41247,6 +41285,7 @@ package android.widget {
method public android.text.TextPaint getPaint();
method public int getPaintFlags();
method public java.lang.String getPrivateImeOptions();
+ method public int[] getRightIndents();
method public int getSelectionEnd();
method public int getSelectionStart();
method public int getShadowColor();
@@ -41324,6 +41363,7 @@ package android.widget {
method public void setImeActionLabel(java.lang.CharSequence, int);
method public void setImeOptions(int);
method public void setIncludeFontPadding(boolean);
+ method public void setIndents(int[], int[]);
method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public void setInputType(int);
method public void setKeyListener(android.text.method.KeyListener);
diff --git a/api/system-current.txt b/api/system-current.txt
index 3204af559a52..2b0f6139eb99 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -866,6 +866,7 @@ package android {
field public static final int layout_x = 16843135; // 0x101017f
field public static final int layout_y = 16843136; // 0x1010180
field public static final int left = 16843181; // 0x10101ad
+ field public static final int leftIndents = 16844016; // 0x10104f0
field public static final int letterSpacing = 16843958; // 0x10104b6
field public static final int lineSpacingExtra = 16843287; // 0x1010217
field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -1094,6 +1095,7 @@ package android {
field public static final int reversible = 16843851; // 0x101044b
field public static final int revisionCode = 16843989; // 0x10104d5
field public static final int right = 16843183; // 0x10101af
+ field public static final int rightIndents = 16844017; // 0x10104f1
field public static final int ringtonePreferenceStyle = 16842899; // 0x1010093
field public static final int ringtoneType = 16843257; // 0x10101f9
field public static final int rotation = 16843558; // 0x1010326
@@ -7140,6 +7142,7 @@ package android.bluetooth {
field public static final int GATT_SERVER = 8; // 0x8
field public static final int HEADSET = 1; // 0x1
field public static final int HEALTH = 3; // 0x3
+ field public static final int SAP = 10; // 0xa
field public static final int STATE_CONNECTED = 2; // 0x2
field public static final int STATE_CONNECTING = 1; // 0x1
field public static final int STATE_DISCONNECTED = 0; // 0x0
@@ -7151,6 +7154,25 @@ package android.bluetooth {
method public abstract void onServiceDisconnected(int);
}
+ public final class BluetoothSap implements android.bluetooth.BluetoothProfile {
+ method public synchronized void close();
+ method public boolean connect(android.bluetooth.BluetoothDevice);
+ method public boolean disconnect(android.bluetooth.BluetoothDevice);
+ method public android.bluetooth.BluetoothDevice getClient();
+ method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ method public int getPriority(android.bluetooth.BluetoothDevice);
+ method public int getState();
+ method public boolean isConnected(android.bluetooth.BluetoothDevice);
+ method public boolean setPriority(android.bluetooth.BluetoothDevice, int);
+ field public static final java.lang.String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED";
+ field public static final int RESULT_CANCELED = 2; // 0x2
+ field public static final int RESULT_FAILURE = 0; // 0x0
+ field public static final int RESULT_SUCCESS = 1; // 0x1
+ field public static final int STATE_ERROR = -1; // 0xffffffff
+ }
+
public final class BluetoothServerSocket implements java.io.Closeable {
method public android.bluetooth.BluetoothSocket accept() throws java.io.IOException;
method public android.bluetooth.BluetoothSocket accept(int) throws java.io.IOException;
@@ -7160,10 +7182,16 @@ package android.bluetooth {
public final class BluetoothSocket implements java.io.Closeable {
method public void close() throws java.io.IOException;
method public void connect() throws java.io.IOException;
+ method public int getConnectionType();
method public java.io.InputStream getInputStream() throws java.io.IOException;
+ method public int getMaxReceivePacketSize();
+ method public int getMaxTransmitPacketSize();
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
method public android.bluetooth.BluetoothDevice getRemoteDevice();
method public boolean isConnected();
+ field public static final int TYPE_L2CAP = 3; // 0x3
+ field public static final int TYPE_RFCOMM = 1; // 0x1
+ field public static final int TYPE_SCO = 2; // 0x2
}
}
@@ -19564,6 +19592,7 @@ package android.net {
public class ConnectivityManager {
method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
method public boolean bindProcessToNetwork(android.net.Network);
+ method public android.net.Network getActiveNetwork();
method public android.net.NetworkInfo getActiveNetworkInfo();
method public android.net.NetworkInfo[] getAllNetworkInfo();
method public android.net.Network[] getAllNetworks();
@@ -19582,7 +19611,8 @@ package android.net {
method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
method public void releaseNetworkRequest(android.app.PendingIntent);
method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
- method public void reportBadNetwork(android.net.Network);
+ method public deprecated void reportBadNetwork(android.net.Network);
+ method public void reportNetworkConnectivity(android.net.Network, boolean);
method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
method public deprecated boolean requestRouteToHost(int, int);
@@ -33087,6 +33117,7 @@ package android.telephony {
method public boolean isWorldPhone();
method public void listen(android.telephony.PhoneStateListener, int);
method public boolean needsOtaServiceProvisioning();
+ method public void notifyCarrierNetworkChange(boolean);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void setDataEnabled(boolean);
method public void setDataEnabled(int, boolean);
@@ -36602,6 +36633,7 @@ package android.view {
method public boolean onTouchEvent(android.view.MotionEvent);
method public void setIsLongpressEnabled(boolean);
method public void setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener);
+ method public void setOnStylusButtonPressListener(android.view.GestureDetector.OnStylusButtonPressListener);
}
public static abstract interface GestureDetector.OnDoubleTapListener {
@@ -36619,7 +36651,11 @@ package android.view {
method public abstract boolean onSingleTapUp(android.view.MotionEvent);
}
- public static class GestureDetector.SimpleOnGestureListener implements android.view.GestureDetector.OnDoubleTapListener android.view.GestureDetector.OnGestureListener {
+ public static abstract interface GestureDetector.OnStylusButtonPressListener {
+ method public abstract boolean onStylusButtonPress(android.view.MotionEvent);
+ }
+
+ public static class GestureDetector.SimpleOnGestureListener implements android.view.GestureDetector.OnDoubleTapListener android.view.GestureDetector.OnGestureListener android.view.GestureDetector.OnStylusButtonPressListener {
ctor public GestureDetector.SimpleOnGestureListener();
method public boolean onDoubleTap(android.view.MotionEvent);
method public boolean onDoubleTapEvent(android.view.MotionEvent);
@@ -36630,6 +36666,7 @@ package android.view {
method public void onShowPress(android.view.MotionEvent);
method public boolean onSingleTapConfirmed(android.view.MotionEvent);
method public boolean onSingleTapUp(android.view.MotionEvent);
+ method public boolean onStylusButtonPress(android.view.MotionEvent);
}
public class Gravity {
@@ -43692,6 +43729,7 @@ package android.widget {
method public int getInputType();
method public final android.text.method.KeyListener getKeyListener();
method public final android.text.Layout getLayout();
+ method public int[] getLeftIndents();
method public float getLetterSpacing();
method public int getLineBounds(int, android.graphics.Rect);
method public int getLineCount();
@@ -43714,6 +43752,7 @@ package android.widget {
method public android.text.TextPaint getPaint();
method public int getPaintFlags();
method public java.lang.String getPrivateImeOptions();
+ method public int[] getRightIndents();
method public int getSelectionEnd();
method public int getSelectionStart();
method public int getShadowColor();
@@ -43791,6 +43830,7 @@ package android.widget {
method public void setImeActionLabel(java.lang.CharSequence, int);
method public void setImeOptions(int);
method public void setIncludeFontPadding(boolean);
+ method public void setIndents(int[], int[]);
method public void setInputExtras(int) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public void setInputType(int);
method public void setKeyListener(android.text.method.KeyListener);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index fe6e4f36d99d..b9ddff0b06d1 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -67,6 +67,8 @@ import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
import android.util.ArrayMap;
import android.util.Log;
import android.view.Display;
@@ -1427,6 +1429,61 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
+ public @NonNull VolumeInfo getApplicationCurrentVolume(ApplicationInfo app) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ if (app.isInternal()) {
+ return Preconditions.checkNotNull(
+ storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL));
+ } else if (app.isExternalAsec()) {
+ final List<VolumeInfo> vols = storage.getVolumes();
+ for (VolumeInfo vol : vols) {
+ if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) {
+ return vol;
+ }
+ }
+ throw new IllegalStateException("Failed to find primary public volume");
+ } else {
+ return Preconditions.checkNotNull(storage.findVolumeByUuid(app.volumeUuid));
+ }
+ }
+
+ @Override
+ public @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app) {
+ final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final List<VolumeInfo> vols = storage.getVolumes();
+ final List<VolumeInfo> candidates = new ArrayList<>();
+ for (VolumeInfo vol : vols) {
+ if (isCandidateVolume(app, vol)) {
+ candidates.add(vol);
+ }
+ }
+ return candidates;
+ }
+
+ private static boolean isCandidateVolume(ApplicationInfo app, VolumeInfo vol) {
+ // Private internal is always an option
+ if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
+ return true;
+ }
+
+ // System apps and apps demanding internal storage can't be moved
+ // anywhere else
+ if (app.isSystemApp()
+ || app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+ return false;
+ }
+
+ // Moving into an ASEC on public primary is only an option when app is
+ // internal, or already in ASEC
+ if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) {
+ return app.isInternal() || app.isExternalAsec();
+ }
+
+ // Otherwise we can move to any private volume
+ return (vol.getType() == VolumeInfo.TYPE_PRIVATE);
+ }
+
+ @Override
public String getInstallerPackageName(String packageName) {
try {
return mPM.getInstallerPackageName(packageName);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 2b3cf340bc54..2418e8242593 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2009-2014 The Android Open Source Project
+ * Copyright (C) 2009-2015 The Android Open Source Project
+ * Copyright (C) 2015 Samsung LSI
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +32,9 @@ import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.app.ActivityThread;
+import android.os.SystemProperties;
+import android.os.Binder;
import android.util.Log;
import android.util.Pair;
@@ -152,6 +156,24 @@ public final class BluetoothAdapter {
public static final int STATE_TURNING_OFF = 13;
/**
+ * Indicates the local Bluetooth adapter is turning Bluetooth LE mode on.
+ * @hide
+ */
+ public static final int STATE_BLE_TURNING_ON = 14;
+
+ /**
+ * Indicates the local Bluetooth adapter is in LE only mode.
+ * @hide
+ */
+ public static final int STATE_BLE_ON = 15;
+
+ /**
+ * Indicates the local Bluetooth adapter is turning off LE only mode.
+ * @hide
+ */
+ public static final int STATE_BLE_TURNING_OFF = 16;
+
+ /**
* Activity Action: Show a system activity that requests discoverable mode.
* This activity will also request the user to turn on Bluetooth if it
* is not currently enabled.
@@ -362,6 +384,39 @@ public final class BluetoothAdapter {
public static final String EXTRA_PREVIOUS_CONNECTION_STATE =
"android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE";
+ /**
+ * Broadcast Action: The Bluetooth adapter state has changed in LE only mode.
+ * @hide
+ */
+ public static final String ACTION_BLE_STATE_CHANGED =
+ "anrdoid.bluetooth.adapter.action.BLE_STATE_CHANGED";
+
+ /**
+ * Broadcast Action: The notifys Bluetooth ACL connected event. This will be
+ * by BLE Always on enabled application to know the ACL_CONNECTED event
+ * when Bluetooth state in STATE_BLE_ON. This denotes GATT connection
+ * as Bluetooth LE is the only feature available in STATE_BLE_ON
+ *
+ * This is counterpart of {@link BluetoothDevice#ACTION_ACL_CONNECTED} which
+ * works in Bluetooth state STATE_ON
+ * @hide
+ */
+ public static final String ACTION_BLE_ACL_CONNECTED =
+ "android.bluetooth.adapter.action.BLE_ACL_CONNECTED";
+
+ /**
+ * Broadcast Action: The notifys Bluetooth ACL connected event. This will be
+ * by BLE Always on enabled application to know the ACL_DISCONNECTED event
+ * when Bluetooth state in STATE_BLE_ON. This denotes GATT disconnection as Bluetooth
+ * LE is the only feature available in STATE_BLE_ON
+ *
+ * This is counterpart of {@link BluetoothDevice#ACTION_ACL_DISCONNECTED} which
+ * works in Bluetooth state STATE_ON
+ * @hide
+ */
+ public static final String ACTION_BLE_ACL_DISCONNECTED =
+ "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED";
+
/** The profile is in disconnected state */
public static final int STATE_DISCONNECTED = 0;
/** The profile is in connecting state */
@@ -373,6 +428,19 @@ public final class BluetoothAdapter {
/** @hide */
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
+ private final IBinder mToken;
+
+
+ /** When creating a ServerSocket using listenUsingRfcommOn() or
+ * listenUsingL2capOn() use SOCKET_CHANNEL_AUTO_STATIC to create
+ * a ServerSocket that auto assigns a channel number to the first
+ * bluetooth socket.
+ * The channel number assigned to this first Bluetooth Socket will
+ * be stored in the ServerSocket, and reused for subsequent Bluetooth
+ * sockets.
+ * @hide */
+ public static final int SOCKET_CHANNEL_AUTO_STATIC_NO_SDP = -2;
+
private static final int ADDRESS_LENGTH = 17;
@@ -431,6 +499,7 @@ public final class BluetoothAdapter {
} catch (RemoteException e) {Log.e(TAG, "", e);}
mManagerService = managerService;
mLeScanClients = new HashMap<LeScanCallback, ScanCallback>();
+ mToken = new Binder();
}
/**
@@ -477,11 +546,9 @@ public final class BluetoothAdapter {
* on this device before calling this method.
*/
public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
- if (getState() != STATE_ON) {
- return null;
- }
+ if (!getLeAccess()) return null;
if (!isMultipleAdvertisementSupported() && !isPeripheralModeSupported()) {
- Log.e(TAG, "bluetooth le advertising not supported");
+ Log.e(TAG, "Bluetooth LE advertising not supported");
return null;
}
synchronized(mLock) {
@@ -496,9 +563,7 @@ public final class BluetoothAdapter {
* Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations.
*/
public BluetoothLeScanner getBluetoothLeScanner() {
- if (getState() != STATE_ON) {
- return null;
- }
+ if (!getLeAccess()) return null;
synchronized(mLock) {
if (sBluetoothLeScanner == null) {
sBluetoothLeScanner = new BluetoothLeScanner(mManagerService);
@@ -516,7 +581,6 @@ public final class BluetoothAdapter {
* @return true if the local adapter is turned on
*/
public boolean isEnabled() {
-
try {
synchronized(mManagerCallback) {
if (mService != null) return mService.isEnabled();
@@ -526,6 +590,178 @@ public final class BluetoothAdapter {
}
/**
+ * Return true if Bluetooth LE(Always BLE On feature) is currently
+ * enabled and ready for use
+ * <p>This returns true if current state is either STATE_ON or STATE_BLE_ON
+ *
+ * @return true if the local Bluetooth LE adapter is turned on
+ * @hide
+ */
+ public boolean isLeEnabled() {
+ final int state = getLeState();
+ if (state == BluetoothAdapter.STATE_ON) {
+ if (DBG) Log.d (TAG, "STATE_ON");
+ } else if (state == BluetoothAdapter.STATE_BLE_ON) {
+ if (DBG) Log.d (TAG, "STATE_BLE_ON");
+ } else {
+ if (DBG) Log.d (TAG, "STATE_OFF");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Performs action based on user action to turn BT ON
+ * or OFF if BT is in BLE_ON state
+ */
+ private void notifyUserAction(boolean enable) {
+ if (mService == null) {
+ Log.e(TAG, "mService is null");
+ return;
+ }
+
+ try {
+ if (enable) {
+ mService.onLeServiceUp(); //NA:TODO implementation pending
+ } else {
+ mService.onBrEdrDown(); //NA:TODO implementation pending
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+
+ /**
+ * Returns true if LE only mode is enabled, that is apps
+ * have authorization to turn only BT ON and the calling
+ * app has privilage to do so
+ */
+ private boolean isLEAlwaysOnEnabled() {
+ boolean ret = false;
+ if (SystemProperties.getBoolean("ro.bluetooth.blealwayson", true) == true) {
+ Log.v(TAG, "LE always on mode is enabled");
+ // TODO: System API authorization check
+ ret = true;
+ } else {
+ Log.v(TAG, "LE always on mode is disabled");
+ ret = false;
+ }
+ return ret;
+ }
+
+ /**
+ * Turns off Bluetooth LE which was earlier turned on by calling EnableBLE().
+ *
+ * <p> If the internal Adapter state is STATE_BLE_ON, this would trigger the transition
+ * to STATE_OFF and completely shut-down Bluetooth
+ *
+ * <p> If the Adapter state is STATE_ON, This would unregister the existance of
+ * special Bluetooth LE application and hence the further turning off of Bluetooth
+ * from UI would ensure the complete turn-off of Bluetooth rather than staying back
+ * BLE only state
+ *
+ * <p>This is an asynchronous call: it will return immediately, and
+ * clients should listen for {@link #ACTION_BLE_STATE_CHANGED}
+ * to be notified of subsequent adapter state changes If this call returns
+ * true, then the adapter state will immediately transition from {@link
+ * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time
+ * later transition to either {@link #STATE_BLE_ON} or {@link
+ * #STATE_OFF} based on the existance of the further Always BLE ON enabled applications
+ * If this call returns false then there was an
+ * immediate problem that will prevent the QAdapter from being turned off -
+ * such as the QAadapter already being turned off.
+ *
+ * @return true to indicate success, or false on
+ * immediate error
+ * @hide
+ */
+ public boolean disableBLE() {
+ if (isLEAlwaysOnEnabled() != true) return false;
+
+ int state = getLeState();
+ if (state == BluetoothAdapter.STATE_ON) {
+ if (DBG) Log.d (TAG, "STATE_ON: shouldn't disable");
+ try {
+ mManagerService.updateBleAppCount(mToken, false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ return true;
+
+ } else if (state == BluetoothAdapter.STATE_BLE_ON) {
+ if (DBG) Log.d (TAG, "STATE_BLE_ON");
+ int bleAppCnt = 0;
+ try {
+ bleAppCnt = mManagerService.updateBleAppCount(mToken, false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ if (bleAppCnt == 0) {
+ // Disable only if there are no other clients
+ notifyUserAction(false);
+ }
+ return true;
+ }
+
+ if (DBG) Log.d (TAG, "STATE_OFF: Already disabled");
+ return false;
+ }
+
+ /**
+ * Special Applications who want to only turn on Bluetooth Low Energy (BLE) would
+ * EnableBLE, EnableBLE brings-up Bluetooth so that application can access
+ * only LE related feature (Bluetooth GATT layers interfaces using the respective class)
+ * EnableBLE in turn registers the existance of a special App which wants to
+ * turn on Bluetooth Low enrgy part without making it visible at the settings UI
+ * as Bluetooth ON.
+ * <p>Invoking EnableBLE when Bluetooth is already in ON state, would just registers
+ * the existance of special Application and doesn't do anything to current BT state.
+ * when user turn OFF Bluetooth from UI, if there is an existance of special app, Bluetooth
+ * would stay in BLE_ON state so that LE features are still acessible to the special
+ * Applications.
+ *
+ * <p>This is an asynchronous call: it will return immediately, and
+ * clients should listen for {@link #ACTION_BLE_STATE_CHANGED}
+ * to be notified of subsequent adapter state changes. If this call returns
+ * true, then the adapter state will immediately transition from {@link
+ * #STATE_OFF} to {@link #STATE_BLE_TURNING_ON}, and some time
+ * later transition to either {@link #STATE_OFF} or {@link
+ * #STATE_BLE_ON}. If this call returns false then there was an
+ * immediate problem that will prevent the adapter from being turned on -
+ * such as Airplane mode, or the adapter is already turned on.
+ * (@link #ACTION_BLE_STATE_CHANGED) returns the Bluetooth Adapter's various
+ * states, It includes all the classic Bluetooth Adapter states along with
+ * internal BLE only states
+ *
+ * @return true to indicate Bluetooth LE start-up has begun, or false on
+ * immediate error
+ * @hide
+ */
+ public boolean enableBLE() {
+ if (isLEAlwaysOnEnabled() != true) return false;
+
+ if (isLeEnabled() == true) {
+ if (DBG) Log.d(TAG, "enableBLE(): BT is already enabled..!");
+ try {
+ mManagerService.updateBleAppCount(mToken, true);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ return true;
+ }
+
+ try {
+ if (DBG) Log.d(TAG, "Calling enableBLE");
+ mManagerService.updateBleAppCount(mToken, true);
+ return mManagerService.enable();
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+
+ return false;
+ }
+
+ /**
* Get the current state of the local Bluetooth adapter.
* <p>Possible return values are
* {@link #STATE_OFF},
@@ -543,6 +779,13 @@ public final class BluetoothAdapter {
{
int state= mService.getState();
if (VDBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state);
+ //consider all internal states as OFF
+ if (state == BluetoothAdapter.STATE_BLE_ON
+ || state == BluetoothAdapter.STATE_BLE_TURNING_ON
+ || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
+ if (VDBG) Log.d(TAG, "Consider internal state as OFF");
+ state = BluetoothAdapter.STATE_OFF;
+ }
return state;
}
// TODO(BT) there might be a small gap during STATE_TURNING_ON that
@@ -554,6 +797,49 @@ public final class BluetoothAdapter {
}
/**
+ * Get the current state of the local Bluetooth adapter
+ * <p>This returns current internal state of Adapter including LE ON/OFF
+ *
+ * <p>Possible return values are
+ * {@link #STATE_OFF},
+ * {@link #STATE_BLE_TURNING_ON},
+ * {@link #STATE_BLE_ON},
+ * {@link #STATE_TURNING_ON},
+ * {@link #STATE_ON},
+ * {@link #STATE_TURNING_OFF},
+ * {@link #STATE_BLE_TURNING_OFF}.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @return current state of Bluetooth adapter
+ * @hide
+ */
+ public int getLeState() {
+ try {
+ synchronized(mManagerCallback) {
+ if (mService != null)
+ {
+ int state= mService.getState();
+ if (VDBG) Log.d(TAG,"getLeState() returning " + state);
+ return state;
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ return BluetoothAdapter.STATE_OFF;
+ }
+
+ boolean getLeAccess() {
+ if(getLeState() == STATE_ON)
+ return true;
+
+ else if (getLeState() == STATE_BLE_ON)
+ return true; // TODO: FILTER SYSTEM APPS HERE <--
+
+ return false;
+ }
+
+ /**
* Turn on the local Bluetooth adapter&mdash;do not use without explicit
* user action to turn on Bluetooth.
* <p>This powers on the underlying Bluetooth hardware, and starts all
@@ -581,10 +867,23 @@ public final class BluetoothAdapter {
* immediate error
*/
public boolean enable() {
+ int state = STATE_OFF;
if (isEnabled() == true){
if (DBG) Log.d(TAG, "enable(): BT is already enabled..!");
return true;
}
+ //Use service interface to get the exact state
+ if (mService != null) {
+ try {
+ state = mService.getState();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ }
+
+ if (state == BluetoothAdapter.STATE_BLE_ON) {
+ Log.e(TAG, "BT is in BLE_ON State");
+ notifyUserAction(true);
+ return true;
+ }
try {
return mManagerService.enable();
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -1141,6 +1440,9 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, true, true, channel);
int errno = socket.mSocket.bindListen();
+ if(channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+ socket.setChannel(socket.mSocket.getPort());
+ }
if (errno != 0) {
//TODO(BT): Throw the same exception error code
// that the previous code was using.
@@ -1275,6 +1577,9 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, false, false, port);
int errno = socket.mSocket.bindListen();
+ if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+ socket.setChannel(socket.mSocket.getPort());
+ }
if (errno != 0) {
//TODO(BT): Throw the same exception error code
// that the previous code was using.
@@ -1297,6 +1602,9 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, false, true, port);
int errno = socket.mSocket.bindListen();
+ if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+ socket.setChannel(socket.mSocket.getPort());
+ }
if (errno < 0) {
//TODO(BT): Throw the same exception error code
// that the previous code was using.
@@ -1327,6 +1635,30 @@ public final class BluetoothAdapter {
}
/**
+ * Construct an encrypted, authenticated, L2CAP server socket.
+ * Call #accept to retrieve connections to this socket.
+ * @return An L2CAP BluetoothServerSocket
+ * @throws IOException On error, for example Bluetooth not available, or
+ * insufficient permissions.
+ * @hide
+ */
+ public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException {
+ BluetoothServerSocket socket = new BluetoothServerSocket(
+ BluetoothSocket.TYPE_L2CAP, true, true, port);
+ int errno = socket.mSocket.bindListen();
+ if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+ socket.setChannel(socket.mSocket.getPort());
+ }
+ if (errno != 0) {
+ //TODO(BT): Throw the same exception error code
+ // that the previous code was using.
+ //socket.mSocket.throwErrnoNative(errno);
+ throw new IOException("Error: " + errno);
+ }
+ return socket;
+ }
+
+ /**
* Read the local Out of Band Pairing Data
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
@@ -1405,6 +1737,9 @@ public final class BluetoothAdapter {
} else if (profile == BluetoothProfile.HEADSET_CLIENT) {
BluetoothHeadsetClient headsetClient = new BluetoothHeadsetClient(context, listener);
return true;
+ } else if (profile == BluetoothProfile.SAP) {
+ BluetoothSap sap = new BluetoothSap(context, listener);
+ return true;
} else {
return false;
}
@@ -1469,6 +1804,10 @@ public final class BluetoothAdapter {
BluetoothHeadsetClient headsetClient = (BluetoothHeadsetClient)proxy;
headsetClient.close();
break;
+ case BluetoothProfile.SAP:
+ BluetoothSap sap = (BluetoothSap)proxy;
+ sap.close();
+ break;
}
}
@@ -1512,6 +1851,10 @@ public final class BluetoothAdapter {
}
}
}
+
+ public void onBrEdrDown() {
+ if (VDBG) Log.i(TAG, "on QBrEdrDown: ");
+ }
};
/**
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index bb0d0a39f56a..bfc374fb913c 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -302,6 +302,12 @@ public final class BluetoothDevice implements Parcelable {
*/
public static final int DEVICE_TYPE_DUAL = 3;
+
+ /** @hide */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SDP_RECORD =
+ "android.bluetooth.device.action.SDP_RECORD";
+
/**
* Broadcast Action: This intent is used to broadcast the {@link UUID}
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
@@ -376,6 +382,9 @@ public final class BluetoothDevice implements Parcelable {
/**@hide*/
public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3;
+ /**@hide*/
+ public static final int REQUEST_TYPE_SIM_ACCESS = 4;
+
/**
* Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents,
* Contains package name to return reply intent to.
@@ -526,6 +535,13 @@ public final class BluetoothDevice implements Parcelable {
*/
public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
+ /** @hide */
+ public static final String EXTRA_SDP_RECORD =
+ "android.bluetooth.device.extra.SDP_RECORD";
+
+ /** @hide */
+ public static final String EXTRA_SDP_SEARCH_STATUS =
+ "android.bluetooth.device.extra.SDP_SEARCH_STATUS";
/**
* For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
* {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
@@ -593,7 +609,9 @@ public final class BluetoothDevice implements Parcelable {
public void onBluetoothServiceUp(IBluetooth bluetoothService)
throws RemoteException {
synchronized (BluetoothDevice.class) {
- sService = bluetoothService;
+ if (sService == null) {
+ sService = bluetoothService;
+ }
}
}
@@ -603,6 +621,11 @@ public final class BluetoothDevice implements Parcelable {
sService = null;
}
}
+
+ public void onBrEdrDown()
+ {
+ if (DBG) Log.d(TAG, "onBrEdrDown: reached BLE ON state");
+ }
};
/**
* Create a new BluetoothDevice
@@ -1017,7 +1040,7 @@ public final class BluetoothDevice implements Parcelable {
* or null on error
*/
public ParcelUuid[] getUuids() {
- if (sService == null) {
+ if (sService == null || isBluetoothEnabled() == false) {
Log.e(TAG, "BT not enabled. Cannot get remote device Uuids");
return null;
}
@@ -1044,7 +1067,7 @@ public final class BluetoothDevice implements Parcelable {
*/
public boolean fetchUuidsWithSdp() {
IBluetooth service = sService;
- if (service == null) {
+ if (service == null || isBluetoothEnabled() == false) {
Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp");
return false;
}
@@ -1054,28 +1077,38 @@ public final class BluetoothDevice implements Parcelable {
return false;
}
+ /**
+ * Perform a service discovery on the remote device to get the SDP records associated
+ * with the specified UUID.
+ *
+ * <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent,
+ * with the SDP records found on the remote end. If there is an error
+ * in getting the SDP records or if the process takes a long time,
+ * {@link #ACTION_SDP_RECORD} intent is sent with an status value in
+ * {@link #EXTRA_SDP_SEARCH_STATUS} different from 0.
+ * Detailed status error codes can be found by members of the Bluetooth package in
+ * the AbstractionLayer class.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
+ * The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
+ * The object type will match one of the SdpXxxRecord types, depending on the UUID searched
+ * for.
+ *
+ * @return False if the sanity check fails, True if the process
+ * of initiating an ACL connection to the remote device
+ * was started.
+ */
/** @hide */
- public boolean fetchMasInstances() {
+ public boolean sdpSearch(ParcelUuid uuid) {
if (sService == null) {
- Log.e(TAG, "BT not enabled. Cannot query remote device for MAS instances");
+ Log.e(TAG, "BT not enabled. Cannot query remote device sdp records");
return false;
}
try {
- return sService.fetchRemoteMasInstances(this);
+ return sService.sdpSearch(this,uuid);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
- /** @hide */
- public int getServiceChannel(ParcelUuid uuid) {
- //TODO(BT)
- /*
- try {
- return sService.getRemoteServiceChannel(this, uuid);
- } catch (RemoteException e) {Log.e(TAG, "", e);}*/
- return BluetoothDevice.ERROR;
- }
-
/**
* Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
@@ -1154,6 +1187,15 @@ public final class BluetoothDevice implements Parcelable {
return false;
}
+ boolean isBluetoothEnabled() {
+ boolean ret = false;
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null && adapter.isEnabled() == true) {
+ ret = true;
+ }
+ return ret;
+ }
+
/**
* Requires {@link android.Manifest.permission#BLUETOOTH}.
* @return Whether the phonebook access is allowed to this device. Can be
@@ -1231,6 +1273,44 @@ public final class BluetoothDevice implements Parcelable {
}
/**
+ * Requires {@link android.Manifest.permission#BLUETOOTH}.
+ * @return Whether the Sim access is allowed to this device. Can be
+ * {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}.
+ * @hide
+ */
+ public int getSimAccessPermission() {
+ if (sService == null) {
+ return ACCESS_UNKNOWN;
+ }
+ try {
+ return sService.getSimAccessPermission(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ return ACCESS_UNKNOWN;
+ }
+
+ /**
+ * Sets whether the Sim access is allowed to this device.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+ * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or
+ * {@link #ACCESS_REJECTED}.
+ * @return Whether the value has been successfully set.
+ * @hide
+ */
+ public boolean setSimAccessPermission(int value) {
+ if (sService == null) {
+ return false;
+ }
+ try {
+ return sService.setSimAccessPermission(this, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ return false;
+ }
+
+ /**
* Create an RFCOMM {@link BluetoothSocket} ready to start a secure
* outgoing connection to this remote device on given channel.
* <p>The remote device will be authenticated and communication on this
@@ -1256,11 +1336,45 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public BluetoothSocket createRfcommSocket(int channel) throws IOException {
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel,
null);
}
/**
+ * Create an L2cap {@link BluetoothSocket} ready to start a secure
+ * outgoing connection to this remote device on given channel.
+ * <p>The remote device will be authenticated and communication on this
+ * socket will be encrypted.
+ * <p> Use this socket only if an authenticated socket link is possible.
+ * Authentication refers to the authentication of the link key to
+ * prevent man-in-the-middle type of attacks.
+ * For example, for Bluetooth 2.1 devices, if any of the devices does not
+ * have an input and output capability or just has the ability to
+ * display a numeric key, a secure socket connection is not possible.
+ * In such a case, use {#link createInsecureRfcommSocket}.
+ * For more details, refer to the Security Model section 5.2 (vol 3) of
+ * Bluetooth Core Specification version 2.1 + EDR.
+ * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
+ * connection.
+ * <p>Valid L2CAP PSM channels are in range 1 to 2^16.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @param channel L2cap PSM/channel to connect to
+ * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
+ * @throws IOException on error, for example Bluetooth not available, or
+ * insufficient permissions
+ * @hide
+ */
+ public BluetoothSocket createL2capSocket(int channel) throws IOException {
+ return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel,
+ null);
+ }
+
+ /**
* Create an RFCOMM {@link BluetoothSocket} ready to start a secure
* outgoing connection to this remote device using SDP lookup of uuid.
* <p>This is designed to be used with {@link
@@ -1292,6 +1406,11 @@ public final class BluetoothDevice implements Parcelable {
* insufficient permissions
*/
public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
+
return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1,
new ParcelUuid(uuid));
}
@@ -1325,6 +1444,10 @@ public final class BluetoothDevice implements Parcelable {
* insufficient permissions
*/
public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException {
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, -1,
new ParcelUuid(uuid));
}
@@ -1344,6 +1467,11 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
+
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port,
null);
}
@@ -1359,6 +1487,11 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public BluetoothSocket createScoSocket() throws IOException {
+
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1, null);
}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 136740505a4a..eecb07396572 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -103,17 +103,23 @@ public interface BluetoothProfile {
*/
public static final int MAP = 9;
+ /*
+ * SAP Profile
+ * @hide
+ */
+ public static final int SAP = 10;
+
/**
* A2DP Sink Profile
* @hide
*/
- public static final int A2DP_SINK = 10;
+ public static final int A2DP_SINK = 11;
/**
* AVRCP Controller Profile
* @hide
*/
- public static final int AVRCP_CONTROLLER = 11;
+ public static final int AVRCP_CONTROLLER = 12;
/**
* Headset Client - HFP HF Role
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
new file mode 100644
index 000000000000..7b4c6f923ffc
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -0,0 +1,383 @@
+/*
+ * 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 android.bluetooth;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+
+public final class BluetoothSap implements BluetoothProfile {
+
+ private static final String TAG = "BluetoothSap";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ public static final String ACTION_CONNECTION_STATE_CHANGED =
+ "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED";
+
+ private IBluetoothSap mService;
+ private final Context mContext;
+ private ServiceListener mServiceListener;
+ private BluetoothAdapter mAdapter;
+
+ /** There was an error trying to obtain the state */
+ public static final int STATE_ERROR = -1;
+
+ public static final int RESULT_FAILURE = 0;
+ public static final int RESULT_SUCCESS = 1;
+ /** Connection canceled before completion. */
+ public static final int RESULT_CANCELED = 2;
+
+ final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ if (VDBG) Log.d(TAG,"Unbinding service...");
+ synchronized (mConnection) {
+ try {
+ mService = null;
+ mContext.unbindService(mConnection);
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ } else {
+ synchronized (mConnection) {
+ try {
+ if (mService == null) {
+ if (VDBG) Log.d(TAG,"Binding service...");
+ doBind();
+ }
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Create a BluetoothSap proxy object.
+ */
+ /*package*/ BluetoothSap(Context context, ServiceListener l) {
+ if (DBG) Log.d(TAG, "Create BluetoothSap proxy object");
+ mContext = context;
+ mServiceListener = l;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+ doBind();
+ }
+
+ boolean doBind() {
+ Intent intent = new Intent(IBluetoothMap.class.getName());
+ ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+ intent.setComponent(comp);
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
+ Log.e(TAG, "Could not bind to Bluetooth SAP Service with " + intent);
+ return false;
+ }
+ return true;
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Close the connection to the backing service.
+ * Other public functions of BluetoothSap will return default error
+ * results once close() has been called. Multiple invocations of close()
+ * are ok.
+ */
+ public synchronized void close() {
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (Exception e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ synchronized (mConnection) {
+ if (mService != null) {
+ try {
+ mService = null;
+ mContext.unbindService(mConnection);
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ mServiceListener = null;
+ }
+
+ /**
+ * Get the current state of the BluetoothSap service.
+ * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
+ * object is currently not connected to the Sap service.
+ */
+ public int getState() {
+ if (VDBG) log("getState()");
+ if (mService != null) {
+ try {
+ return mService.getState();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ }
+ return BluetoothSap.STATE_ERROR;
+ }
+
+ /**
+ * Get the currently connected remote Bluetooth device (PCE).
+ * @return The remote Bluetooth device, or null if not in connected or
+ * connecting state, or if this proxy object is not connected to
+ * the Sap service.
+ */
+ public BluetoothDevice getClient() {
+ if (VDBG) log("getClient()");
+ if (mService != null) {
+ try {
+ return mService.getClient();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the specified Bluetooth device is connected.
+ * Returns false if not connected, or if this proxy object is not
+ * currently connected to the Sap service.
+ */
+ public boolean isConnected(BluetoothDevice device) {
+ if (VDBG) log("isConnected(" + device + ")");
+ if (mService != null) {
+ try {
+ return mService.isConnected(device);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Initiate connection. Initiation of outgoing connections is not
+ * supported for SAP server.
+ */
+ public boolean connect(BluetoothDevice device) {
+ if (DBG) log("connect(" + device + ")" + "not supported for SAPS");
+ return false;
+ }
+
+ /**
+ * Initiate disconnect.
+ *
+ * @param device Remote Bluetooth Device
+ * @return false on error,
+ * true otherwise
+ */
+ public boolean disconnect(BluetoothDevice device) {
+ if (DBG) log("disconnect(" + device + ")");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ try {
+ return mService.disconnect(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Get the list of connected devices. Currently at most one.
+ *
+ * @return list of connected devices
+ */
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (DBG) log("getConnectedDevices()");
+ if (mService != null && isEnabled()) {
+ try {
+ return mService.getConnectedDevices();
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return new ArrayList<BluetoothDevice>();
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return new ArrayList<BluetoothDevice>();
+ }
+
+ /**
+ * Get the list of devices matching specified states. Currently at most one.
+ *
+ * @return list of matching devices
+ */
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (DBG) log("getDevicesMatchingStates()");
+ if (mService != null && isEnabled()) {
+ try {
+ return mService.getDevicesMatchingConnectionStates(states);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return new ArrayList<BluetoothDevice>();
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return new ArrayList<BluetoothDevice>();
+ }
+
+ /**
+ * Get connection state of device
+ *
+ * @return device connection state
+ */
+ public int getConnectionState(BluetoothDevice device) {
+ if (DBG) log("getConnectionState(" + device + ")");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ try {
+ return mService.getConnectionState(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ /**
+ * Set priority of the profile
+ *
+ * <p> The device should already be paired.
+ *
+ * @param device Paired bluetooth device
+ * @param priority
+ * @return true if priority is set, false on error
+ */
+ public boolean setPriority(BluetoothDevice device, int priority) {
+ if (DBG) log("setPriority(" + device + ", " + priority + ")");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ if (priority != BluetoothProfile.PRIORITY_OFF &&
+ priority != BluetoothProfile.PRIORITY_ON) {
+ return false;
+ }
+ try {
+ return mService.setPriority(device, priority);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Get the priority of the profile.
+ *
+ * @param device Bluetooth device
+ * @return priority of the device
+ */
+ public int getPriority(BluetoothDevice device) {
+ if (VDBG) log("getPriority(" + device + ")");
+ if (mService != null && isEnabled() &&
+ isValidDevice(device)) {
+ try {
+ return mService.getPriority(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return PRIORITY_OFF;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return PRIORITY_OFF;
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (DBG) log("Proxy object connected");
+ mService = IBluetoothSap.Stub.asInterface(service);
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.SAP, BluetoothSap.this);
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) log("Proxy object disconnected");
+ mService = null;
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.SAP);
+ }
+ }
+ };
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+
+ private boolean isEnabled() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+ if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON)
+ return true;
+ log("Bluetooth is Not enabled");
+ return false;
+ }
+
+ private boolean isValidDevice(BluetoothDevice device) {
+ if (device == null)
+ return false;
+
+ if (BluetoothAdapter.checkBluetoothAddress(device.getAddress()))
+ return true;
+ return false;
+ }
+
+}
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index bc56e556492b..21024a6021b0 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -18,6 +18,7 @@ package android.bluetooth;
import android.os.Handler;
import android.os.ParcelUuid;
+import android.util.Log;
import java.io.Closeable;
import java.io.IOException;
@@ -66,10 +67,11 @@ import java.io.IOException;
*/
public final class BluetoothServerSocket implements Closeable {
+ private static final String TAG = "BluetoothServerSocket";
/*package*/ final BluetoothSocket mSocket;
private Handler mHandler;
private int mMessage;
- private final int mChannel;
+ private int mChannel;
/**
* Construct a socket for incoming connections.
@@ -84,6 +86,9 @@ public final class BluetoothServerSocket implements Closeable {
throws IOException {
mChannel = port;
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null);
+ if(port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+ mSocket.setExcludeSdp(true);
+ }
}
/**
@@ -98,6 +103,7 @@ public final class BluetoothServerSocket implements Closeable {
/*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid)
throws IOException {
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid);
+ // TODO: This is the same as mChannel = -1 - is this intentional?
mChannel = mSocket.getPort();
}
@@ -153,6 +159,7 @@ public final class BluetoothServerSocket implements Closeable {
/*package*/ void setServiceName(String ServiceName) {
mSocket.setServiceName(ServiceName);
}
+
/**
* Returns the channel on which this socket is bound.
* @hide
@@ -160,4 +167,47 @@ public final class BluetoothServerSocket implements Closeable {
public int getChannel() {
return mChannel;
}
+
+ /**
+ * Sets the channel on which future sockets are bound.
+ * Currently used only when a channel is auto generated.
+ */
+ /*package*/ void setChannel(int newChannel) {
+ /* TODO: From a design/architecture perspective this is wrong.
+ * The bind operation should be conducted through this class
+ * and the resulting port should be kept in mChannel, and
+ * not set from BluetoothAdapter. */
+ if(mSocket != null) {
+ if(mSocket.getPort() != newChannel) {
+ Log.w(TAG,"The port set is different that the underlying port. mSocket.getPort(): "
+ + mSocket.getPort() + " requested newChannel: " + newChannel);
+ }
+ }
+ mChannel = newChannel;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ServerSocket: Type: ");
+ switch(mSocket.getConnectionType()) {
+ case BluetoothSocket.TYPE_RFCOMM:
+ {
+ sb.append("TYPE_RFCOMM");
+ break;
+ }
+ case BluetoothSocket.TYPE_L2CAP:
+ {
+ sb.append("TYPE_L2CAP");
+ break;
+ }
+ case BluetoothSocket.TYPE_SCO:
+ {
+ sb.append("TYPE_SCO");
+ break;
+ }
+ }
+ sb.append(" Channel: ").append(mChannel);
+ return sb.toString();
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 36997e544841..5702d117b66c 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -21,6 +21,7 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
+import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -29,6 +30,8 @@ import java.io.OutputStream;
import java.util.Locale;
import java.util.UUID;
import android.net.LocalSocket;
+
+import java.nio.Buffer;
import java.nio.ByteOrder;
import java.nio.ByteBuffer;
/**
@@ -86,17 +89,19 @@ public final class BluetoothSocket implements Closeable {
/** @hide */
public static final int MAX_RFCOMM_CHANNEL = 30;
+ /*package*/ static final int MAX_L2CAP_PACKAGE_SIZE = 0xFFFF;
/** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
- /*package*/ static final int TYPE_RFCOMM = 1;
- /*package*/ static final int TYPE_SCO = 2;
- /*package*/ static final int TYPE_L2CAP = 3;
+ public static final int TYPE_RFCOMM = 1;
+ public static final int TYPE_SCO = 2;
+ public static final int TYPE_L2CAP = 3;
/*package*/ static final int EBADFD = 77;
/*package*/ static final int EADDRINUSE = 98;
/*package*/ static final int SEC_FLAG_ENCRYPT = 1;
/*package*/ static final int SEC_FLAG_AUTH = 1 << 1;
+ /*package*/ static final int BTSOCK_FLAG_NO_SDP = 1 << 2;
private final int mType; /* one of TYPE_RFCOMM etc */
private BluetoothDevice mDevice; /* remote device */
@@ -106,6 +111,7 @@ public final class BluetoothSocket implements Closeable {
private final BluetoothInputStream mInputStream;
private final BluetoothOutputStream mOutputStream;
private final ParcelUuid mUuid;
+ private boolean mExcludeSdp = false;
private ParcelFileDescriptor mPfd;
private LocalSocket mSocket;
private InputStream mSocketIS;
@@ -115,7 +121,11 @@ public final class BluetoothSocket implements Closeable {
private String mServiceName;
private static int PROXY_CONNECTION_TIMEOUT = 5000;
- private static int SOCK_SIGNAL_SIZE = 16;
+ private static int SOCK_SIGNAL_SIZE = 20;
+
+ private ByteBuffer mL2capBuffer = null;
+ private int mMaxTxPacketSize = 0; // The l2cap maximum packet size supported by the peer.
+ private int mMaxRxPacketSize = 0; // The l2cap maximum packet size that can be received.
private enum SocketState {
INIT,
@@ -144,12 +154,14 @@ public final class BluetoothSocket implements Closeable {
*/
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
- if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
+ if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type);
+ if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1
+ && port != BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
throw new IOException("Invalid RFCOMM channel: " + port);
}
}
- if(uuid != null)
+ if (uuid != null)
mUuid = uuid;
else mUuid = new ParcelUuid(new UUID(0, 0));
mType = type;
@@ -172,6 +184,7 @@ public final class BluetoothSocket implements Closeable {
mOutputStream = new BluetoothOutputStream(this);
}
private BluetoothSocket(BluetoothSocket s) {
+ if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType);
mUuid = s.mUuid;
mType = s.mType;
mAuth = s.mAuth;
@@ -179,7 +192,11 @@ public final class BluetoothSocket implements Closeable {
mPort = s.mPort;
mInputStream = new BluetoothInputStream(this);
mOutputStream = new BluetoothOutputStream(this);
+ mMaxRxPacketSize = s.mMaxRxPacketSize;
+ mMaxTxPacketSize = s.mMaxTxPacketSize;
+
mServiceName = s.mServiceName;
+ mExcludeSdp = s.mExcludeSdp;
}
private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
BluetoothSocket as = new BluetoothSocket(this);
@@ -229,6 +246,8 @@ public final class BluetoothSocket implements Closeable {
flags |= SEC_FLAG_AUTH;
if(mEncrypt)
flags |= SEC_FLAG_ENCRYPT;
+ if(mExcludeSdp)
+ flags |= BTSOCK_FLAG_NO_SDP;
return flags;
}
@@ -298,7 +317,8 @@ public final class BluetoothSocket implements Closeable {
try {
if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
- IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+ IBluetooth bluetoothProxy =
+ BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
mPfd = bluetoothProxy.connectSocket(mDevice, mType,
mUuid, mPort, getSecurityFlags());
@@ -370,7 +390,7 @@ public final class BluetoothSocket implements Closeable {
mSocketState = SocketState.LISTENING;
}
if (DBG) Log.d(TAG, "channel: " + channel);
- if (mPort == -1) {
+ if (mPort <= -1) {
mPort = channel;
} // else ASSERT(mPort == channel)
ret = 0;
@@ -391,7 +411,8 @@ public final class BluetoothSocket implements Closeable {
/*package*/ BluetoothSocket accept(int timeout) throws IOException {
BluetoothSocket acceptedSocket;
- if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state");
+ if (mSocketState != SocketState.LISTENING)
+ throw new IOException("bt socket is not in listen state");
if(timeout > 0) {
Log.d(TAG, "accept() set timeout (ms):" + timeout);
mSocket.setSoTimeout(timeout);
@@ -427,27 +448,80 @@ public final class BluetoothSocket implements Closeable {
}
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
- if (mSocketIS == null) throw new IOException("read is called on null InputStream");
+ int ret = 0;
if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length);
- int ret = mSocketIS.read(b, offset, length);
- if(ret < 0)
+ if(mType == TYPE_L2CAP)
+ {
+ int bytesToRead = length;
+ if (VDBG) Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length
+ + "mL2capBuffer= " + mL2capBuffer);
+ if (mL2capBuffer == null) {
+ createL2capRxBuffer();
+ }
+ if (mL2capBuffer.remaining() == 0) {
+ if (VDBG) Log.v(TAG, "l2cap buffer empty, refilling...");
+ if (fillL2capRxBuffer() == -1) {
+ return -1;
+ }
+ }
+ if (bytesToRead > mL2capBuffer.remaining()) {
+ bytesToRead = mL2capBuffer.remaining();
+ }
+ if(VDBG) Log.v(TAG, "get(): offset: " + offset
+ + " bytesToRead: " + bytesToRead);
+ mL2capBuffer.get(b, offset, bytesToRead);
+ ret = bytesToRead;
+ }else {
+ if (VDBG) Log.v(TAG, "default: read(): offset: " + offset + " length:" + length);
+ ret = mSocketIS.read(b, offset, length);
+ }
+ if (ret < 0)
throw new IOException("bt socket closed, read return: " + ret);
if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret);
return ret;
}
/*package*/ int write(byte[] b, int offset, int length) throws IOException {
- if (mSocketOS == null) throw new IOException("write is called on null OutputStream");
- if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
- mSocketOS.write(b, offset, length);
- // There is no good way to confirm since the entire process is asynchronous anyway
- if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
- return length;
+
+ //TODO: Since bindings can exist between the SDU size and the
+ // protocol, we might need to throw an exception instead of just
+ // splitting the write into multiple smaller writes.
+ // Rfcomm uses dynamic allocation, and should not have any bindings
+ // to the actual message length.
+ if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
+ if (mType == TYPE_L2CAP) {
+ if(length <= mMaxTxPacketSize) {
+ mSocketOS.write(b, offset, length);
+ } else {
+ int tmpOffset = offset;
+ int tmpLength = mMaxTxPacketSize;
+ int endIndex = offset + length;
+ boolean done = false;
+ if(DBG) Log.w(TAG, "WARNING: Write buffer larger than L2CAP packet size!\n"
+ + "Packet will be divided into SDU packets of size "
+ + mMaxTxPacketSize);
+ do{
+ mSocketOS.write(b, tmpOffset, tmpLength);
+ tmpOffset += mMaxTxPacketSize;
+ if((tmpOffset + mMaxTxPacketSize) > endIndex) {
+ tmpLength = endIndex - tmpOffset;
+ done = true;
+ }
+ } while(!done);
+
+ }
+ } else {
+ mSocketOS.write(b, offset, length);
+ }
+ // There is no good way to confirm since the entire process is asynchronous anyway
+ if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
+ return length;
}
@Override
public void close() throws IOException {
- if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
+ if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: "
+ + mSocketState);
if(mSocketState == SocketState.CLOSED)
return;
else
@@ -457,8 +531,9 @@ public final class BluetoothSocket implements Closeable {
if(mSocketState == SocketState.CLOSED)
return;
mSocketState = SocketState.CLOSED;
- if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
- ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
+ if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort +
+ ", mSocketIS: " + mSocketIS + ", mSocketOS: " + mSocketOS +
+ "mSocket: " + mSocket);
if(mSocket != null) {
if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
mSocket.shutdownInput();
@@ -480,6 +555,47 @@ public final class BluetoothSocket implements Closeable {
/*package */ int getPort() {
return mPort;
}
+
+ /**
+ * Get the maximum supported Transmit packet size for the underlying transport.
+ * Use this to optimize the writes done to the output socket, to avoid sending
+ * half full packets.
+ * @return the maximum supported Transmit packet size for the underlying transport.
+ */
+ public int getMaxTransmitPacketSize(){
+ return mMaxTxPacketSize;
+ }
+
+ /**
+ * Get the maximum supported Receive packet size for the underlying transport.
+ * Use this to optimize the reads done on the input stream, as any call to read
+ * will return a maximum of this amount of bytes - or for some transports a
+ * multiple of this value.
+ * @return the maximum supported Receive packet size for the underlying transport.
+ */
+ public int getMaxReceivePacketSize(){
+ return mMaxRxPacketSize;
+ }
+
+ /**
+ * Get the type of the underlying connection
+ * @return one of TYPE_
+ */
+ public int getConnectionType() {
+ return mType;
+ }
+
+ /**
+ * Change if a SDP entry should be automatically created.
+ * Must be called before calling .bind, for the call to have any effect.
+ * @param mExcludeSdp <li>TRUE - do not auto generate SDP record.
+ * <li>FALSE - default - auto generate SPP SDP record.
+ * @hide
+ */
+ public void setExcludeSdp(boolean excludeSdp) {
+ this.mExcludeSdp = excludeSdp;
+ }
+
private String convertAddr(final byte[] addr) {
return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]);
@@ -487,8 +603,10 @@ public final class BluetoothSocket implements Closeable {
private String waitSocketSignal(InputStream is) throws IOException {
byte [] sig = new byte[SOCK_SIGNAL_SIZE];
int ret = readAll(is, sig);
- if (VDBG) Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret);
+ if (VDBG) Log.d(TAG, "waitSocketSignal read " + SOCK_SIGNAL_SIZE +
+ " bytes signal ret: " + ret);
ByteBuffer bb = ByteBuffer.wrap(sig);
+ /* the struct in native is decorated with __attribute__((packed)), hence this is possible */
bb.order(ByteOrder.nativeOrder());
int size = bb.getShort();
if(size != SOCK_SIGNAL_SIZE)
@@ -497,19 +615,36 @@ public final class BluetoothSocket implements Closeable {
bb.get(addr);
int channel = bb.getInt();
int status = bb.getInt();
+ mMaxTxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
+ mMaxRxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
String RemoteAddr = convertAddr(addr);
if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
- + RemoteAddr + ", channel: " + channel + ", status: " + status);
+ + RemoteAddr + ", channel: " + channel + ", status: " + status
+ + " MaxRxPktSize: " + mMaxRxPacketSize + " MaxTxPktSize: " + mMaxTxPacketSize);
if(status != 0)
throw new IOException("Connection failure, status: " + status);
return RemoteAddr;
}
+
+ private void createL2capRxBuffer(){
+ if(mType == TYPE_L2CAP) {
+ // Allocate the buffer to use for reads.
+ if(VDBG) Log.v(TAG, " Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize);
+ mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]);
+ if(VDBG) Log.v(TAG, "mL2capBuffer.remaining()" + mL2capBuffer.remaining());
+ mL2capBuffer.limit(0); // Ensure we do a real read at the first read-request
+ if(VDBG) Log.v(TAG, "mL2capBuffer.remaining() after limit(0):" +
+ mL2capBuffer.remaining());
+ }
+ }
+
private int readAll(InputStream is, byte[] b) throws IOException {
int left = b.length;
while(left > 0) {
int ret = is.read(b, b.length - left, left);
if(ret <= 0)
- throw new IOException("read failed, socket might closed or timeout, read ret: " + ret);
+ throw new IOException("read failed, socket might closed or timeout, read ret: "
+ + ret);
left -= ret;
if(left != 0)
Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) +
@@ -526,4 +661,18 @@ public final class BluetoothSocket implements Closeable {
bb.order(ByteOrder.nativeOrder());
return bb.getInt();
}
+
+ private int fillL2capRxBuffer() throws IOException {
+ mL2capBuffer.rewind();
+ int ret = mSocketIS.read(mL2capBuffer.array());
+ if(ret == -1) {
+ // reached end of stream - return -1
+ mL2capBuffer.limit(0);
+ return -1;
+ }
+ mL2capBuffer.limit(ret);
+ return ret;
+ }
+
+
}
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 194a53e0f6cb..2ded4c8fea32 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -76,7 +76,9 @@ public final class BluetoothUuid {
ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid MAS =
ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
-
+ public static final ParcelUuid SAP =
+ ParcelUuid.fromString("0000112D-0000-1000-8000-00805F9B34FB");
+
public static final ParcelUuid BASE_UUID =
ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
@@ -89,7 +91,7 @@ public final class BluetoothUuid {
public static final ParcelUuid[] RESERVED_UUIDS = {
AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
- ObexObjectPush, PANU, NAP, MAP, MNS, MAS};
+ ObexObjectPush, PANU, NAP, MAP, MNS, MAS, SAP};
public static boolean isAudioSource(ParcelUuid uuid) {
return uuid.equals(AudioSource);
@@ -143,6 +145,9 @@ public final class BluetoothUuid {
public static boolean isMas(ParcelUuid uuid) {
return uuid.equals(MAS);
}
+ public static boolean isSap(ParcelUuid uuid) {
+ return uuid.equals(SAP);
+ }
/**
* Returns true if ParcelUuid is present in uuidArray
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 299f4c8e327a..f6001bfffedd 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -68,7 +68,7 @@ interface IBluetooth
int getRemoteClass(in BluetoothDevice device);
ParcelUuid[] getRemoteUuids(in BluetoothDevice device);
boolean fetchRemoteUuids(in BluetoothDevice device);
- boolean fetchRemoteMasInstances(in BluetoothDevice device);
+ boolean sdpSearch(in BluetoothDevice device, in ParcelUuid uuid);
boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode);
boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[]
@@ -79,6 +79,8 @@ interface IBluetooth
boolean setPhonebookAccessPermission(in BluetoothDevice device, int value);
int getMessageAccessPermission(in BluetoothDevice device);
boolean setMessageAccessPermission(in BluetoothDevice device, int value);
+ int getSimAccessPermission(in BluetoothDevice device);
+ boolean setSimAccessPermission(in BluetoothDevice device, int value);
void sendConnectionStateChange(in BluetoothDevice device, int profile, int state, int prevState);
@@ -102,4 +104,6 @@ interface IBluetooth
// for dumpsys support
String dump();
+ void onLeServiceUp();
+ void onBrEdrDown();
}
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 7070baebcf79..4ca57f8eccab 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -101,4 +101,6 @@ interface IBluetoothGatt {
in int srvcInstanceId, in ParcelUuid srvcId,
in int charInstanceId, in ParcelUuid charId,
in boolean confirm, in byte[] value);
+ void disconnectAll();
+ void unregAll();
}
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index 7411d3f27b54..8d1ce990a562 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -44,4 +44,6 @@ interface IBluetoothManager
String getAddress();
String getName();
+ int updateBleAppCount(IBinder b, boolean enable);
+ boolean isBleAppPresent();
}
diff --git a/core/java/android/bluetooth/IBluetoothManagerCallback.aidl b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl
index 9551086a1de7..1385dafca2fb 100644
--- a/core/java/android/bluetooth/IBluetoothManagerCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl
@@ -26,4 +26,5 @@ import android.bluetooth.IBluetooth;
interface IBluetoothManagerCallback {
void onBluetoothServiceUp(in IBluetooth bluetoothService);
void onBluetoothServiceDown();
-} \ No newline at end of file
+ void onBrEdrDown();
+}
diff --git a/core/java/android/bluetooth/IBluetoothSap.aidl b/core/java/android/bluetooth/IBluetoothSap.aidl
new file mode 100644
index 000000000000..8970639467c0
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothSap.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * System private API for Bluetooth SAP service
+ *
+ * {@hide}
+ */
+interface IBluetoothSap {
+ int getState();
+ BluetoothDevice getClient();
+ boolean connect(in BluetoothDevice device);
+ boolean disconnect(in BluetoothDevice device);
+ boolean isConnected(in BluetoothDevice device);
+ List<BluetoothDevice> getConnectedDevices();
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+ int getConnectionState(in BluetoothDevice device);
+ boolean setPriority(in BluetoothDevice device, int priority);
+ int getPriority(in BluetoothDevice device);
+}
diff --git a/core/java/android/bluetooth/SdpMasRecord.java b/core/java/android/bluetooth/SdpMasRecord.java
new file mode 100644
index 000000000000..fa164c0fa9d0
--- /dev/null
+++ b/core/java/android/bluetooth/SdpMasRecord.java
@@ -0,0 +1,147 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SdpMasRecord implements Parcelable {
+ private final int mMasInstanceId;
+ private final int mL2capPsm;
+ private final int mRfcommChannelNumber;
+ private final int mProfileVersion;
+ private final int mSupportedFeatures;
+ private final int mSupportedMessageTypes;
+ private final String mServiceName;
+ public static final class MessageType {
+ public static final int EMAIL = 0x01;
+ public static final int SMS_GSM = 0x02;
+ public static final int SMS_CDMA = 0x04;
+ public static final int MMS = 0x08;
+ }
+
+ public SdpMasRecord(int mas_instance_id,
+ int l2cap_psm,
+ int rfcomm_channel_number,
+ int profile_version,
+ int supported_features,
+ int supported_message_types,
+ String service_name){
+ this.mMasInstanceId = mas_instance_id;
+ this.mL2capPsm = l2cap_psm;
+ this.mRfcommChannelNumber = rfcomm_channel_number;
+ this.mProfileVersion = profile_version;
+ this.mSupportedFeatures = supported_features;
+ this.mSupportedMessageTypes = supported_message_types;
+ this.mServiceName = service_name;
+ }
+
+ public SdpMasRecord(Parcel in){
+ this.mMasInstanceId = in.readInt();
+ this.mL2capPsm = in.readInt();
+ this.mRfcommChannelNumber = in.readInt();
+ this.mProfileVersion = in.readInt();
+ this.mSupportedFeatures = in.readInt();
+ this.mSupportedMessageTypes = in.readInt();
+ this.mServiceName = in.readString();
+ }
+ @Override
+ public int describeContents() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int getMasInstanceId() {
+ return mMasInstanceId;
+ }
+
+ public int getL2capPsm() {
+ return mL2capPsm;
+ }
+
+ public int getRfcommCannelNumber() {
+ return mRfcommChannelNumber;
+ }
+
+ public int getProfileVersion() {
+ return mProfileVersion;
+ }
+
+ public int getSupportedFeatures() {
+ return mSupportedFeatures;
+ }
+
+ public int getSupportedMessageTypes() {
+ return mSupportedMessageTypes;
+ }
+
+ public boolean msgSupported(int msg) {
+ return (mSupportedMessageTypes & msg) != 0;
+ }
+
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+
+ dest.writeInt(this.mMasInstanceId);
+ dest.writeInt(this.mL2capPsm);
+ dest.writeInt(this.mRfcommChannelNumber);
+ dest.writeInt(this.mProfileVersion);
+ dest.writeInt(this.mSupportedFeatures);
+ dest.writeInt(this.mSupportedMessageTypes);
+ dest.writeString(this.mServiceName);
+
+ }
+ @Override
+ public String toString(){
+ String ret = "Bluetooth MAS SDP Record:\n";
+
+ if(mMasInstanceId != -1){
+ ret += "Mas Instance Id: " + mMasInstanceId + "\n";
+ }
+ if(mRfcommChannelNumber != -1){
+ ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
+ }
+ if(mL2capPsm != -1){
+ ret += "L2CAP PSM: " + mL2capPsm + "\n";
+ }
+ if(mServiceName != null){
+ ret += "Service Name: " + mServiceName + "\n";
+ }
+ if(mProfileVersion != -1){
+ ret += "Profile version: " + mProfileVersion + "\n";
+ }
+ if(mSupportedMessageTypes != -1){
+ ret += "Supported msg types: " + mSupportedMessageTypes + "\n";
+ }
+ if(mSupportedFeatures != -1){
+ ret += "Supported features: " + mSupportedFeatures + "\n";
+ }
+ return ret;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public SdpMasRecord createFromParcel(Parcel in) {
+ return new SdpMasRecord(in);
+ }
+ public SdpRecord[] newArray(int size) {
+ return new SdpRecord[size];
+ }
+ };
+}
diff --git a/core/java/android/bluetooth/SdpMnsRecord.java b/core/java/android/bluetooth/SdpMnsRecord.java
new file mode 100644
index 000000000000..c02bb5a18417
--- /dev/null
+++ b/core/java/android/bluetooth/SdpMnsRecord.java
@@ -0,0 +1,112 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SdpMnsRecord implements Parcelable {
+ private final int mL2capPsm;
+ private final int mRfcommChannelNumber;
+ private final int mSupportedFeatures;
+ private final int mProfileVersion;
+ private final String mServiceName;
+
+ public SdpMnsRecord(int l2cap_psm,
+ int rfcomm_channel_number,
+ int profile_version,
+ int supported_features,
+ String service_name){
+ this.mL2capPsm = l2cap_psm;
+ this.mRfcommChannelNumber = rfcomm_channel_number;
+ this.mSupportedFeatures = supported_features;
+ this.mServiceName = service_name;
+ this.mProfileVersion = profile_version;
+ }
+
+ public SdpMnsRecord(Parcel in){
+ this.mRfcommChannelNumber = in.readInt();
+ this.mL2capPsm = in.readInt();
+ this.mServiceName = in.readString();
+ this.mSupportedFeatures = in.readInt();
+ this.mProfileVersion = in.readInt();
+ }
+ @Override
+ public int describeContents() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+
+ public int getL2capPsm() {
+ return mL2capPsm;
+ }
+
+ public int getRfcommChannelNumber() {
+ return mRfcommChannelNumber;
+ }
+
+ public int getSupportedFeatures() {
+ return mSupportedFeatures;
+ }
+
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ public int getProfileVersion() {
+ return mProfileVersion;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRfcommChannelNumber);
+ dest.writeInt(mL2capPsm);
+ dest.writeString(mServiceName);
+ dest.writeInt(mSupportedFeatures);
+ dest.writeInt(mProfileVersion);
+ }
+
+ public String toString(){
+ String ret = "Bluetooth MNS SDP Record:\n";
+
+ if(mRfcommChannelNumber != -1){
+ ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
+ }
+ if(mL2capPsm != -1){
+ ret += "L2CAP PSM: " + mL2capPsm + "\n";
+ }
+ if(mServiceName != null){
+ ret += "Service Name: " + mServiceName + "\n";
+ }
+ if(mSupportedFeatures != -1){
+ ret += "Supported features: " + mSupportedFeatures + "\n";
+ }
+ if(mProfileVersion != -1){
+ ret += "Profile_version: " + mProfileVersion+"\n";
+ }
+ return ret;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public SdpMnsRecord createFromParcel(Parcel in) {
+ return new SdpMnsRecord(in);
+ }
+ public SdpMnsRecord[] newArray(int size) {
+ return new SdpMnsRecord[size];
+ }
+ };
+}
diff --git a/core/java/android/bluetooth/SdpOppOpsRecord.java b/core/java/android/bluetooth/SdpOppOpsRecord.java
new file mode 100644
index 000000000000..e0e4007a215c
--- /dev/null
+++ b/core/java/android/bluetooth/SdpOppOpsRecord.java
@@ -0,0 +1,118 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.bluetooth;
+
+import java.util.Arrays;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data representation of a Object Push Profile Server side SDP record.
+ */
+/** @hide */
+public class SdpOppOpsRecord implements Parcelable {
+
+ private final String mServiceName;
+ private final int mRfcommChannel;
+ private final int mL2capPsm;
+ private final int mProfileVersion;
+ private final byte[] mFormatsList;
+
+ public SdpOppOpsRecord(String serviceName, int rfcommChannel,
+ int l2capPsm, int version, byte[] formatsList) {
+ super();
+ this.mServiceName = serviceName;
+ this.mRfcommChannel = rfcommChannel;
+ this.mL2capPsm = l2capPsm;
+ this.mProfileVersion = version;
+ this.mFormatsList = formatsList;
+ }
+
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ public int getRfcommChannel() {
+ return mRfcommChannel;
+ }
+
+ public int getL2capPsm() {
+ return mL2capPsm;
+ }
+
+ public int getProfileVersion() {
+ return mProfileVersion;
+ }
+
+ public byte[] getFormatsList() {
+ return mFormatsList;
+ }
+
+ @Override
+ public int describeContents() {
+ /* No special objects */
+ return 0;
+ }
+
+ public SdpOppOpsRecord(Parcel in){
+ this.mRfcommChannel = in.readInt();
+ this.mL2capPsm = in.readInt();
+ this.mProfileVersion = in.readInt();
+ this.mServiceName = in.readString();
+ int arrayLength = in.readInt();
+ if(arrayLength > 0) {
+ byte[] bytes = new byte[arrayLength];
+ in.readByteArray(bytes);
+ this.mFormatsList = bytes;
+ } else {
+ this.mFormatsList = null;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRfcommChannel);
+ dest.writeInt(mL2capPsm);
+ dest.writeInt(mProfileVersion);
+ dest.writeString(mServiceName);
+ if(mFormatsList!= null && mFormatsList.length > 0) {
+ dest.writeInt(mFormatsList.length);
+ dest.writeByteArray(mFormatsList);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ public String toString(){
+ StringBuilder sb = new StringBuilder("Bluetooth OPP Server SDP Record:\n");
+ sb.append(" RFCOMM Chan Number: ").append(mRfcommChannel);
+ sb.append("\n L2CAP PSM: ").append(mL2capPsm);
+ sb.append("\n Profile version: ").append(mProfileVersion);
+ sb.append("\n Service Name: ").append(mServiceName);
+ sb.append("\n Formats List: ").append(Arrays.toString(mFormatsList));
+ return sb.toString();
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public SdpOppOpsRecord createFromParcel(Parcel in) {
+ return new SdpOppOpsRecord(in);
+ }
+ public SdpOppOpsRecord[] newArray(int size) {
+ return new SdpOppOpsRecord[size];
+ }
+ };
+
+}
diff --git a/core/java/android/bluetooth/SdpPseRecord.java b/core/java/android/bluetooth/SdpPseRecord.java
new file mode 100644
index 000000000000..2c159ccb83c1
--- /dev/null
+++ b/core/java/android/bluetooth/SdpPseRecord.java
@@ -0,0 +1,125 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SdpPseRecord implements Parcelable {
+ private final int mL2capPsm;
+ private final int mRfcommChannelNumber;
+ private final int mProfileVersion;
+ private final int mSupportedFeatures;
+ private final int mSupportedRepositories;
+ private final String mServiceName;
+
+ public SdpPseRecord(int l2cap_psm,
+ int rfcomm_channel_number,
+ int profile_version,
+ int supported_features,
+ int supported_repositories,
+ String service_name){
+ this.mL2capPsm = l2cap_psm;
+ this.mRfcommChannelNumber = rfcomm_channel_number;
+ this.mProfileVersion = profile_version;
+ this.mSupportedFeatures = supported_features;
+ this.mSupportedRepositories = supported_repositories;
+ this.mServiceName = service_name;
+ }
+
+ public SdpPseRecord(Parcel in){
+ this.mRfcommChannelNumber = in.readInt();
+ this.mL2capPsm = in.readInt();
+ this.mProfileVersion = in.readInt();
+ this.mSupportedFeatures = in.readInt();
+ this.mSupportedRepositories = in.readInt();
+ this.mServiceName = in.readString();
+ }
+ @Override
+ public int describeContents() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public int getL2capPsm() {
+ return mL2capPsm;
+ }
+
+ public int getRfcommChannelNumber() {
+ return mRfcommChannelNumber;
+ }
+
+ public int getSupportedFeatures() {
+ return mSupportedFeatures;
+ }
+
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ public int getProfileVersion() {
+ return mProfileVersion;
+ }
+
+ public int getSupportedRepositories() {
+ return mSupportedRepositories;
+ }
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRfcommChannelNumber);
+ dest.writeInt(mL2capPsm);
+ dest.writeInt(mProfileVersion);
+ dest.writeInt(mSupportedFeatures);
+ dest.writeInt(mSupportedRepositories);
+ dest.writeString(mServiceName);
+
+ }
+
+ public String toString(){
+ String ret = "Bluetooth MNS SDP Record:\n";
+
+ if(mRfcommChannelNumber != -1){
+ ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
+ }
+ if(mL2capPsm != -1){
+ ret += "L2CAP PSM: " + mL2capPsm + "\n";
+ }
+ if(mProfileVersion != -1){
+ ret += "profile version: " + mProfileVersion + "\n";
+ }
+ if(mServiceName != null){
+ ret += "Service Name: " + mServiceName + "\n";
+ }
+ if(mSupportedFeatures != -1){
+ ret += "Supported features: " + mSupportedFeatures + "\n";
+ }
+ if(mSupportedRepositories != -1){
+ ret += "Supported repositories: " + mSupportedRepositories + "\n";
+ }
+
+ return ret;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public SdpPseRecord createFromParcel(Parcel in) {
+ return new SdpPseRecord(in);
+ }
+ public SdpPseRecord[] newArray(int size) {
+ return new SdpPseRecord[size];
+ }
+ };
+}
diff --git a/core/java/android/bluetooth/SdpRecord.java b/core/java/android/bluetooth/SdpRecord.java
new file mode 100644
index 000000000000..6f1065e38d26
--- /dev/null
+++ b/core/java/android/bluetooth/SdpRecord.java
@@ -0,0 +1,76 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES 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.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/** @hide */
+public class SdpRecord implements Parcelable{
+
+ private final byte[] mRawData;
+ private final int mRawSize;
+
+ @Override
+ public String toString() {
+ return "BluetoothSdpRecord [rawData=" + Arrays.toString(mRawData)
+ + ", rawSize=" + mRawSize + "]";
+ }
+
+ public SdpRecord(int size_record, byte[] record){
+ this.mRawData = record;
+ this.mRawSize = size_record;
+ }
+
+ public SdpRecord(Parcel in){
+ this.mRawSize = in.readInt();
+ this.mRawData = new byte[mRawSize];
+ in.readByteArray(this.mRawData);
+
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(this.mRawSize);
+ dest.writeByteArray(this.mRawData);
+
+
+ }
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public SdpRecord createFromParcel(Parcel in) {
+ return new SdpRecord(in);
+ }
+
+ public SdpRecord[] newArray(int size) {
+ return new SdpRecord[size];
+ }
+ };
+
+ public byte[] getRawData() {
+ return mRawData;
+ }
+
+ public int getRawSize() {
+ return mRawSize;
+ }
+}
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index b6bcfb8d7a76..307895129120 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -409,8 +409,8 @@ public final class BluetoothLeScanner {
List <ScanFilter> filterList) {
final int callbackType = settings.getCallbackType();
// If onlost/onfound is requested, a non-empty filter is expected
- if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH
- | ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) {
+ if ((callbackType & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH
+ | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) != 0) {
if (filterList == null) {
return false;
}
diff --git a/core/java/android/bluetooth/le/BluetoothLeUtils.java b/core/java/android/bluetooth/le/BluetoothLeUtils.java
index 4916bd9cebfa..c40256b89069 100644
--- a/core/java/android/bluetooth/le/BluetoothLeUtils.java
+++ b/core/java/android/bluetooth/le/BluetoothLeUtils.java
@@ -132,7 +132,7 @@ public class BluetoothLeUtils {
* {@link BluetoothAdapter#STATE_ON}.
*/
static void checkAdapterStateOn(BluetoothAdapter adapter) {
- if (adapter == null || adapter.getState() != BluetoothAdapter.STATE_ON) {
+ if (adapter == null || !adapter.isLeEnabled()) {//adapter.getState() != BluetoothAdapter.STATE_ON) {
throw new IllegalStateException("BT Adapter is not turned ON");
}
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 5bdb7bbcf60e..caf069f6fb56 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -21,6 +21,7 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Printer;
import com.android.internal.util.ArrayUtils;
@@ -937,6 +938,17 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
}
+ /** @hide */
+ public boolean isInternal() {
+ return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
+ }
+
+ /** @hide */
+ public boolean isExternalAsec() {
+ return TextUtils.isEmpty(volumeUuid)
+ && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8844ea827315..a12887296cb0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -20,6 +20,7 @@ import android.annotation.CheckResult;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.StringRes;
@@ -4162,6 +4163,12 @@ public abstract class PackageManager {
public abstract void movePackageAndData(String packageName, String volumeUuid,
IPackageMoveObserver observer);
+ /** {@hide} */
+ public abstract @Nullable VolumeInfo getApplicationCurrentVolume(ApplicationInfo app);
+
+ /** {@hide} */
+ public abstract @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app);
+
/**
* Returns the device identity that verifiers can use to associate their scheme to a particular
* device. This should not be used by anything other than a package verifier.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 55e39b1f8d83..b34160061ca2 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -611,6 +611,27 @@ public class ConnectivityManager {
}
/**
+ * Returns a {@link Network} object corresponding to the currently active
+ * default data network. In the event that the current active default data
+ * network disconnects, the returned {@code Network} object will no longer
+ * be usable. This will return {@code null} when there is no default
+ * network.
+ *
+ * @return a {@link Network} object for the current default network or
+ * {@code null} if no default network is currently active
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
+ public Network getActiveNetwork() {
+ try {
+ return mService.getActiveNetwork();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
* Returns details about the currently active default data network
* for a given uid. This is for internal use only to avoid spying
* other apps.
@@ -831,48 +852,6 @@ public class ConnectivityManager {
}
/**
- * Tells each network type to set its radio power state as directed.
- *
- * @param turnOn a boolean, {@code true} to turn the radios on,
- * {@code false} to turn them off.
- * @return a boolean, {@code true} indicating success. All network types
- * will be tried, even if some fail.
- *
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
- * {@hide}
- */
-// TODO - check for any callers and remove
-// public boolean setRadios(boolean turnOn) {
-// try {
-// return mService.setRadios(turnOn);
-// } catch (RemoteException e) {
-// return false;
-// }
-// }
-
- /**
- * Tells a given networkType to set its radio power state as directed.
- *
- * @param networkType the int networkType of interest.
- * @param turnOn a boolean, {@code true} to turn the radio on,
- * {@code} false to turn it off.
- * @return a boolean, {@code true} indicating success.
- *
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
- * {@hide}
- */
-// TODO - check for any callers and remove
-// public boolean setRadio(int networkType, boolean turnOn) {
-// try {
-// return mService.setRadio(networkType, turnOn);
-// } catch (RemoteException e) {
-// return false;
-// }
-// }
-
- /**
* Tells the underlying networking system that the caller wants to
* begin using the named feature. The interpretation of {@code feature}
* is completely up to each networking implementation.
@@ -1738,10 +1717,33 @@ public class ConnectivityManager {
*
* @param network The {@link Network} the application was attempting to use
* or {@code null} to indicate the current default network.
+ * @deprecated Use {@link #reportNetworkConnectivity} which allows reporting both
+ * working and non-working connectivity.
*/
public void reportBadNetwork(Network network) {
try {
- mService.reportBadNetwork(network);
+ // One of these will be ignored because it matches system's current state.
+ // The other will trigger the necessary reevaluation.
+ mService.reportNetworkConnectivity(network, true);
+ mService.reportNetworkConnectivity(network, false);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Report to the framework whether a network has working connectivity.
+ * This provides a hint to the system that a particular network is providing
+ * working connectivity or not. In response the framework may re-evaluate
+ * the network's connectivity and might take further action thereafter.
+ *
+ * @param network The {@link Network} the application was attempting to use
+ * or {@code null} to indicate the current default network.
+ * @param hasConnectivity {@code true} if the application was able to successfully access the
+ * Internet using {@code network} or {@code false} if not.
+ */
+ public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
+ try {
+ mService.reportNetworkConnectivity(network, hasConnectivity);
} catch (RemoteException e) {
}
}
@@ -1978,12 +1980,18 @@ public class ConnectivityManager {
} catch (RemoteException e) { }
}
- /** {@hide} */
- public void registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ /**
+ * @hide
+ * Register a NetworkAgent with ConnectivityService.
+ * @return NetID corresponding to NetworkAgent.
+ */
+ public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
NetworkCapabilities nc, int score, NetworkMisc misc) {
try {
- mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc);
- } catch (RemoteException e) { }
+ return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc);
+ } catch (RemoteException e) {
+ return NETID_UNSET;
+ }
}
/**
@@ -2444,6 +2452,23 @@ public class ConnectivityManager {
}
/**
+ * Request connectivityservice to refresh network capabilities for the given
+ * {@link network}. This method returns true if the network is still active, false
+ * otherwise. Notice the method call assumes the caller has registered for
+ * listening NetworkCapabilities updates.
+ *
+ * @param network{@link Network} specifying which network you're interested.
+ * @hide
+ */
+ public boolean requestBwUpdate(Network network) {
+ try {
+ return mService.requestBwUpdate(network);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
* Unregisters callbacks about and possibly releases networks originating from
* {@link #requestNetwork} and {@link #registerNetworkCallback} calls. If the
* given {@code NetworkCallback} had previously been used with {@code #requestNetwork},
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 1aa9c451f8a8..9d9b1bfa3326 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -43,6 +43,7 @@ import com.android.internal.net.VpnProfile;
/** {@hide} */
interface IConnectivityManager
{
+ Network getActiveNetwork();
NetworkInfo getActiveNetworkInfo();
NetworkInfo getActiveNetworkInfoForUid(int uid);
NetworkInfo getNetworkInfo(int networkType);
@@ -95,7 +96,7 @@ interface IConnectivityManager
void reportInetCondition(int networkType, int percentage);
- void reportBadNetwork(in Network network);
+ void reportNetworkConnectivity(in Network network, boolean hasConnectivity);
ProxyInfo getGlobalProxy();
@@ -121,8 +122,6 @@ interface IConnectivityManager
void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
- int findConnectionTypeForIface(in String iface);
-
int checkMobileProvisioning(int suggestedTimeOutMs);
String getMobileProvisioningUrl();
@@ -135,9 +134,11 @@ interface IConnectivityManager
void registerNetworkFactory(in Messenger messenger, in String name);
+ boolean requestBwUpdate(in Network network);
+
void unregisterNetworkFactory(in Messenger messenger);
- void registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
+ int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
in NetworkCapabilities nc, int score, in NetworkMisc misc);
NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 95ceb2a06425..9c3a623190b7 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -39,12 +39,18 @@ import java.util.ArrayList;
* @hide
*/
public abstract class NetworkAgent extends Handler {
+ // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown
+ // an exception.
+ public final int netId;
+
private volatile AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private static final boolean DBG = true;
private static final boolean VDBG = false;
private final Context mContext;
private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
+ private volatile long mLastBwRefreshTime = 0;
+ private static final long BW_REFRESH_MIN_WIN_MS = 500;
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
@@ -134,6 +140,11 @@ public abstract class NetworkAgent extends Handler {
*/
public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
+ /** Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
+ * the underlying network connection for updated bandwidth information.
+ */
+ public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
+
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
this(looper, context, logTag, ni, nc, lp, score, null);
@@ -151,7 +162,7 @@ public abstract class NetworkAgent extends Handler {
if (VDBG) log("Registering NetworkAgent");
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
- cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
+ netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
new LinkProperties(lp), new NetworkCapabilities(nc), score, misc);
}
@@ -195,6 +206,15 @@ public abstract class NetworkAgent extends Handler {
log("Unhandled Message " + msg);
break;
}
+ case CMD_REQUEST_BANDWIDTH_UPDATE: {
+ if (VDBG) {
+ log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
+ }
+ if (System.currentTimeMillis() > (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
+ pollLceData();
+ }
+ break;
+ }
case CMD_REPORT_NETWORK_STATUS: {
if (VDBG) {
log("CMD_REPORT_NETWORK_STATUS(" +
@@ -240,6 +260,7 @@ public abstract class NetworkAgent extends Handler {
* Called by the bearer code when it has new NetworkCapabilities data.
*/
public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
+ mLastBwRefreshTime = System.currentTimeMillis();
queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
new NetworkCapabilities(networkCapabilities));
}
@@ -294,6 +315,13 @@ public abstract class NetworkAgent extends Handler {
abstract protected void unwanted();
/**
+ * Called when ConnectivityService request a bandwidth update. The parent factory
+ * shall try to overwrite this method and produce a bandwidth update if capable.
+ */
+ protected void pollLceData() {
+ }
+
+ /**
* Called when the system determines the usefulness of this network.
*
* Networks claiming internet connectivity will have their internet
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
index dc96640f82ec..2c60d41acd94 100644
--- a/core/java/android/os/storage/DiskInfo.java
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -26,6 +26,7 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import java.io.CharArrayWriter;
+import java.util.Objects;
/**
* Information about a physical disk which may contain one or more
@@ -118,6 +119,20 @@ public class DiskInfo implements Parcelable {
}
}
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof DiskInfo) {
+ return Objects.equals(id, ((DiskInfo) o).id);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
public static final Creator<DiskInfo> CREATOR = new Creator<DiskInfo>() {
@Override
public DiskInfo createFromParcel(Parcel in) {
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index f06fc8c30b35..a241728557f5 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -38,6 +38,8 @@ import com.android.internal.util.Preconditions;
import java.io.CharArrayWriter;
import java.io.File;
+import java.util.Comparator;
+import java.util.Objects;
/**
* Information about a storage volume that may be mounted. A volume may be a
@@ -77,6 +79,22 @@ public class VolumeInfo implements Parcelable {
private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
+ private static final Comparator<VolumeInfo>
+ sDescriptionComparator = new Comparator<VolumeInfo>() {
+ @Override
+ public int compare(VolumeInfo lhs, VolumeInfo rhs) {
+ if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) {
+ return -1;
+ } else if (lhs.getDescription() == null) {
+ return 1;
+ } else if (rhs.getDescription() == null) {
+ return -1;
+ } else {
+ return lhs.getDescription().compareTo(rhs.getDescription());
+ }
+ }
+ };
+
static {
sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
sStateToEnvironment.put(VolumeInfo.STATE_MOUNTING, Environment.MEDIA_CHECKING);
@@ -150,6 +168,10 @@ public class VolumeInfo implements Parcelable {
return getBroadcastForEnvironment(getEnvironmentForState(state));
}
+ public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() {
+ return sDescriptionComparator;
+ }
+
public @NonNull String getId() {
return id;
}
@@ -344,6 +366,20 @@ public class VolumeInfo implements Parcelable {
}
}
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof VolumeInfo) {
+ return Objects.equals(id, ((VolumeInfo) o).id);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
@Override
public VolumeInfo createFromParcel(Parcel in) {
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 30da0e7652a5..4bd085f2c6ba 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -121,13 +121,10 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
protected void updateSeekBar() {
if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
- mSeekBar.setEnabled(true);
mSeekBar.setProgress(0);
} else if (mMuted) {
- mSeekBar.setEnabled(false);
mSeekBar.setProgress(0);
} else {
- mSeekBar.setEnabled(true);
mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
}
}
@@ -136,6 +133,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_STREAM_VOLUME:
+ if (mMuted && mLastProgress > 0) {
+ mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_UNMUTE, 0);
+ } else if (!mMuted && mLastProgress == 0) {
+ mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_MUTE, 0);
+ }
mAudioManager.setStreamVolume(mStreamType, mLastProgress,
AudioManager.FLAG_SHOW_UI_WARNINGS);
break;
@@ -375,7 +377,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
: (streamType == mStreamType);
if (mSeekBar != null && streamMatch && streamValue != -1) {
- final boolean muted = mAudioManager.isStreamMute(mStreamType);
+ final boolean muted = mAudioManager.isStreamMute(mStreamType)
+ || streamValue == 0;
mUiHandler.postUpdateSlider(streamValue, muted);
}
} else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3707694ebcbb..793971f96fc6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6960,6 +6960,9 @@ public final class Settings {
/** {@hide} */
public static final String
BLUETOOTH_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_";
+ /** {@hide} */
+ public static final String
+ BLUETOOTH_SAP_PRIORITY_PREFIX = "bluetooth_sap_priority_";
/**
* Get the key that retrieves a bluetooth headset's priority.
@@ -6992,6 +6995,15 @@ public final class Settings {
public static final String getBluetoothMapPriorityKey(String address) {
return BLUETOOTH_MAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
}
+
+ /**
+ * Get the key that retrieves a bluetooth map priority.
+ * @hide
+ */
+ public static final String getBluetoothSapPriorityKey(String address) {
+ return BLUETOOTH_SAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
/**
* Scaling factor for normal window animations. Setting to 0 will
* disable window animations.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 7828851a28f3..67794b1c7d5f 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -165,6 +165,19 @@ public class StaticLayout extends Layout {
return this;
}
+ public Builder setIndents(int[] leftIndents, int[] rightIndents) {
+ int leftLen = leftIndents == null ? 0 : leftIndents.length;
+ int rightLen = rightIndents == null ? 0 : rightIndents.length;
+ int[] indents = new int[Math.max(leftLen, rightLen)];
+ for (int i = 0; i < indents.length; i++) {
+ int leftMargin = i < leftLen ? leftIndents[i] : 0;
+ int rightMargin = i < rightLen ? rightIndents[i] : 0;
+ indents[i] = leftMargin + rightMargin;
+ }
+ nSetIndents(mNativePtr, indents);
+ return this;
+ }
+
/**
* Measurement and break iteration is done in native code. The protocol for using
* the native code is as follows.
@@ -1009,6 +1022,8 @@ public class StaticLayout extends Layout {
private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
+ private static native void nSetIndents(long nativePtr, int[] indents);
+
// Set up paragraph text and settings; done as one big method to minimize jni crossings
private static native void nSetupParagraph(long nativePtr, char[] text, int length,
float firstWidth, int firstWidthLineCount, float restWidth,
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
index ec8f802a179f..e9f23536489d 100644
--- a/core/java/android/view/DisplayListCanvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -283,7 +283,7 @@ public class DisplayListCanvas extends Canvas {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
- nDrawPatch(mNativeCanvasWrapper, bitmap.getSkBitmap(), patch.mNativeChunk,
+ nDrawPatch(mNativeCanvasWrapper, bitmap, patch.mNativeChunk,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
@@ -293,11 +293,11 @@ public class DisplayListCanvas extends Canvas {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
- nDrawPatch(mNativeCanvasWrapper, bitmap.getSkBitmap(), patch.mNativeChunk,
+ nDrawPatch(mNativeCanvasWrapper, bitmap, patch.mNativeChunk,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
- private static native void nDrawPatch(long renderer, long bitmap, long chunk,
+ private static native void nDrawPatch(long renderer, Bitmap bitmap, long chunk,
float left, float top, float right, float bottom, long paint);
public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 2351548df75a..b8544c65a788 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -149,12 +149,30 @@ public class GestureDetector {
}
/**
+ * The listener that is used to notify when a stylus button press occurs.
+ */
+ public interface OnStylusButtonPressListener {
+ /**
+ * Notified when a stylus button press occurs. This is when the stylus
+ * is touching the screen and the {@value MotionEvent#BUTTON_SECONDARY}
+ * is pressed.
+ *
+ * @param e The motion event that occurred during the stylus button
+ * press.
+ * @return true if the event is consumed, else false
+ */
+ boolean onStylusButtonPress(MotionEvent e);
+ }
+
+ /**
* A convenience class to extend when you only want to listen for a subset
* of all the gestures. This implements all methods in the
- * {@link OnGestureListener} and {@link OnDoubleTapListener} but does
- * nothing and return {@code false} for all applicable methods.
+ * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnStylusButtonPressListener}
+ * but does nothing and return {@code false} for all applicable methods.
*/
- public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {
+ public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
+ OnStylusButtonPressListener {
+
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@@ -190,6 +208,10 @@ public class GestureDetector {
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
+
+ public boolean onStylusButtonPress(MotionEvent e) {
+ return false;
+ }
}
private int mTouchSlopSquare;
@@ -211,10 +233,12 @@ public class GestureDetector {
private final Handler mHandler;
private final OnGestureListener mListener;
private OnDoubleTapListener mDoubleTapListener;
+ private OnStylusButtonPressListener mStylusButtonListener;
private boolean mStillDown;
private boolean mDeferConfirmSingleTap;
private boolean mInLongPress;
+ private boolean mInStylusButtonPress;
private boolean mAlwaysInTapRegion;
private boolean mAlwaysInBiggerTapRegion;
@@ -358,6 +382,9 @@ public class GestureDetector {
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
+ if (listener instanceof OnStylusButtonPressListener) {
+ setOnStylusButtonPressListener((OnStylusButtonPressListener) listener);
+ }
init(context);
}
@@ -420,6 +447,19 @@ public class GestureDetector {
}
/**
+ * Sets the listener which will be called for stylus button related
+ * gestures.
+ *
+ * @param onStylusButtonPressListener the listener invoked for all the
+ * callbacks, or null to stop listening for stylus button
+ * gestures.
+ */
+ public void setOnStylusButtonPressListener(
+ OnStylusButtonPressListener onStylusButtonPressListener) {
+ mStylusButtonListener = onStylusButtonPressListener;
+ }
+
+ /**
* Set whether longpress is enabled, if this is enabled when a user
* presses and holds down you get a longpress event and nothing further.
* If it's disabled the user can press and hold down and then later
@@ -512,7 +552,18 @@ public class GestureDetector {
break;
case MotionEvent.ACTION_DOWN:
- if (mDoubleTapListener != null) {
+ if (mStylusButtonListener != null
+ && ev.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
+ && (ev.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
+ if (mStylusButtonListener.onStylusButtonPress(ev)) {
+ mInStylusButtonPress = true;
+ handled = true;
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
+ }
+ }
+
+ if (mDoubleTapListener != null && !mInStylusButtonPress) {
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) mHandler.removeMessages(TAP);
if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
@@ -540,8 +591,8 @@ public class GestureDetector {
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
-
- if (mIsLongpressEnabled) {
+
+ if (mIsLongpressEnabled && !mInStylusButtonPress) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ TAP_TIMEOUT + LONGPRESS_TIMEOUT);
@@ -551,7 +602,17 @@ public class GestureDetector {
break;
case MotionEvent.ACTION_MOVE:
- if (mInLongPress) {
+ if (mStylusButtonListener != null && !mInStylusButtonPress && !mInLongPress
+ && ev.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
+ && (ev.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
+ if (mStylusButtonListener.onStylusButtonPress(ev)) {
+ mInStylusButtonPress = true;
+ handled = true;
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
+ }
+ }
+ if (mInLongPress || mInStylusButtonPress) {
break;
}
final float scrollX = mLastFocusX - focusX;
@@ -591,6 +652,9 @@ public class GestureDetector {
} else if (mInLongPress) {
mHandler.removeMessages(TAP);
mInLongPress = false;
+ } else if (mInStylusButtonPress) {
+ mHandler.removeMessages(TAP);
+ mInStylusButtonPress = false;
} else if (mAlwaysInTapRegion) {
handled = mListener.onSingleTapUp(ev);
if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
@@ -649,9 +713,8 @@ public class GestureDetector {
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
- if (mInLongPress) {
- mInLongPress = false;
- }
+ mInLongPress = false;
+ mInStylusButtonPress = false;
}
private void cancelTaps() {
@@ -662,9 +725,8 @@ public class GestureDetector {
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
- if (mInLongPress) {
- mInLongPress = false;
- }
+ mInLongPress = false;
+ mInStylusButtonPress = false;
}
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index cafe05309efb..4d8dce1f121e 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -76,9 +76,54 @@ public final class WebViewFactory {
private static boolean sAddressSpaceReserved = false;
private static PackageInfo sPackageInfo;
+ /** @hide */
+ public static String[] getWebViewPackageNames() {
+ return AppGlobals.getInitialApplication().getResources().getStringArray(
+ com.android.internal.R.array.config_webViewPackageNames);
+ }
+
+ // TODO (gsennton) remove when committing webview xts test change
public static String getWebViewPackageName() {
- return AppGlobals.getInitialApplication().getString(
- com.android.internal.R.string.config_webViewPackageName);
+ String[] webViewPackageNames = getWebViewPackageNames();
+ return webViewPackageNames[webViewPackageNames.length-1];
+ }
+
+ /**
+ * Return the package info of the first package in the webview priority list that contains
+ * webview.
+ *
+ * @hide
+ */
+ public static PackageInfo findPreferredWebViewPackage() {
+ PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+
+ for (String packageName : getWebViewPackageNames()) {
+ try {
+ PackageInfo packageInfo = pm.getPackageInfo(packageName,
+ PackageManager.GET_META_DATA);
+ ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+
+ // If the correct flag is set the package contains webview.
+ if (getWebViewLibrary(applicationInfo) != null) {
+ return packageInfo;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+ throw new AndroidRuntimeException("Could not find a loadable WebView package");
+ }
+
+ private static ApplicationInfo getWebViewApplicationInfo() {
+ if (sPackageInfo == null)
+ return findPreferredWebViewPackage().applicationInfo;
+ else
+ return sPackageInfo.applicationInfo;
+ }
+
+ private static String getWebViewLibrary(ApplicationInfo ai) {
+ if (ai.metaData != null)
+ return ai.metaData.getString("com.android.webview.WebViewLibrary");
+ return null;
}
public static PackageInfo getLoadedPackageInfo() {
@@ -99,6 +144,11 @@ public final class WebViewFactory {
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
try {
+ // First fetch the package info so we can log the webview package version.
+ sPackageInfo = findPreferredWebViewPackage();
+ Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
+ sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");
+
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
loadNativeLibrary();
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
@@ -137,15 +187,10 @@ public final class WebViewFactory {
private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
Application initialApplication = AppGlobals.getInitialApplication();
try {
- // First fetch the package info so we can log the webview package version.
- String packageName = getWebViewPackageName();
- sPackageInfo = initialApplication.getPackageManager().getPackageInfo(packageName, 0);
- Log.i(LOGTAG, "Loading " + packageName + " version " + sPackageInfo.versionName +
- " (code " + sPackageInfo.versionCode + ")");
-
// Construct a package context to load the Java code into the current app.
- Context webViewContext = initialApplication.createPackageContext(packageName,
- Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+ Context webViewContext = initialApplication.createPackageContext(
+ sPackageInfo.packageName,
+ Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
initialApplication.getAssets().addAssetPath(
webViewContext.getApplicationInfo().sourceDir);
ClassLoader clazzLoader = webViewContext.getClassLoader();
@@ -272,10 +317,8 @@ public final class WebViewFactory {
private static String[] getWebViewNativeLibraryPaths()
throws PackageManager.NameNotFoundException {
- final String NATIVE_LIB_FILE_NAME = "libwebviewchromium.so";
-
- PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
- ApplicationInfo ai = pm.getApplicationInfo(getWebViewPackageName(), 0);
+ ApplicationInfo ai = getWebViewApplicationInfo();
+ final String NATIVE_LIB_FILE_NAME = getWebViewLibrary(ai);
String path32;
String path64;
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index e7b6238c91a6..c9d9a8c0513a 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3116,6 +3116,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ private boolean performStylusButtonPressAction(MotionEvent ev) {
+ if (ev.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
+ && ev.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
+ && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
+ final View child = getChildAt(mMotionPosition - mFirstPosition);
+ if (child != null) {
+ final int longPressPosition = mMotionPosition;
+ final long longPressId = mAdapter.getItemId(mMotionPosition);
+ if (performLongPress(child, longPressPosition, longPressId)) {
+ mTouchMode = TOUCH_MODE_REST;
+ setPressed(false);
+ child.setPressed(false);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
boolean performLongPress(final View child,
final int longPressPosition, final long longPressId) {
// CHOICE_MODE_MULTIPLE_MODAL takes over long press.
@@ -3757,8 +3776,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
- && performButtonActionOnTouchDown(ev)) {
- removeCallbacks(mPendingCheckForTap);
+ && (performButtonActionOnTouchDown(ev) || performStylusButtonPressAction(ev))) {
+ removeCallbacks(mPendingCheckForTap);
}
}
@@ -3800,6 +3819,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTouchMode = TOUCH_MODE_DONE_WAITING;
updateSelectorState();
} else if (motionView != null) {
+ if (performStylusButtonPressAction(ev)) {
+ removeCallbacks(mPendingCheckForTap);
+ removeCallbacks(mPendingCheckForLongPress);
+ }
+
// Still within bounds, update the hotspot.
final float[] point = mTmpPoint;
point[0] = x;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 93ccd1db9728..955ad06814cb 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1009,14 +1009,14 @@ public class Editor {
stopSelectionActionMode();
} else {
stopSelectionActionMode();
- startSelectionActionModeWithSelection();
+ startSelectionActionModeWithSelectionAndStartDrag();
}
handled = true;
}
// Start a new selection
if (!handled) {
- handled = startSelectionActionModeWithSelection();
+ handled = startSelectionActionModeWithSelectionAndStartDrag();
}
return handled;
@@ -1686,9 +1686,34 @@ public class Editor {
}
/**
+ * Starts a Selection Action Mode with the current selection and enters drag mode. This should
+ * be used whenever the mode is started from a touch event.
+ *
+ * @return true if the selection mode was actually started.
+ */
+ private boolean startSelectionActionModeWithSelectionAndStartDrag() {
+ boolean selectionStarted = startSelectionActionModeWithSelectionInternal();
+ if (selectionStarted) {
+ getSelectionController().enterDrag();
+ }
+ return selectionStarted;
+ }
+
+ /**
+ * Starts a Selection Action Mode with the current selection and ensures the selection handles
+ * are shown. This should be used when the mode is started from a non-touch event.
+ *
* @return true if the selection mode was actually started.
*/
boolean startSelectionActionModeWithSelection() {
+ boolean selectionStarted = startSelectionActionModeWithSelectionInternal();
+ if (selectionStarted) {
+ getSelectionController().show();
+ }
+ return selectionStarted;
+ }
+
+ private boolean startSelectionActionModeWithSelectionInternal() {
if (mSelectionActionMode != null) {
// Selection action mode is already started
mSelectionActionMode.invalidate();
@@ -1727,10 +1752,6 @@ public class Editor {
imm.showSoftInput(mTextView, 0, null);
}
}
-
- if (selectionStarted) {
- getSelectionController().enterDrag();
- }
return selectionStarted;
}
@@ -3099,6 +3120,7 @@ public class Editor {
if (item.getIntent() != null
&& item.getIntent().getAction().equals(Intent.ACTION_PROCESS_TEXT)) {
item.getIntent().putExtra(Intent.EXTRA_PROCESS_TEXT, mTextView.getSelectedText());
+ mPreserveDetachedSelection = true;
mTextView.startActivityForResult(
item.getIntent(), TextView.PROCESS_TEXT_REQUEST_CODE);
return true;
@@ -4237,7 +4259,7 @@ public class Editor {
boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop;
if (stayedInArea && isPositionOnText(x, y)) {
- startSelectionActionModeWithSelection();
+ startSelectionActionModeWithSelectionAndStartDrag();
mDiscardNextActionUp = true;
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b44a8863eb81..732830027290 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -236,6 +236,9 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
* @attr ref android.R.styleable#TextView_elegantTextHeight
* @attr ref android.R.styleable#TextView_letterSpacing
* @attr ref android.R.styleable#TextView_fontFeatureSettings
+ * @attr ref android.R.styleable#TextView_breakStrategy
+ * @attr ref android.R.styleable#TextView_leftIndents
+ * @attr ref android.R.styleable#TextView_rightIndents
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -551,6 +554,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private float mSpacingAdd = 0.0f;
private int mBreakStrategy;
+ private int[] mLeftIndents;
+ private int[] mRightIndents;
private int mMaximum = Integer.MAX_VALUE;
private int mMaxMode = LINES;
@@ -1146,6 +1151,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextView_breakStrategy:
mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
+ break;
+
+ case com.android.internal.R.styleable.TextView_leftIndents:
+ TypedArray margins = res.obtainTypedArray(a.getResourceId(attr, View.NO_ID));
+ mLeftIndents = parseDimensionArray(margins);
+ break;
+
+ case com.android.internal.R.styleable.TextView_rightIndents:
+ margins = res.obtainTypedArray(a.getResourceId(attr, View.NO_ID));
+ mRightIndents = parseDimensionArray(margins);
+ break;
}
}
a.recycle();
@@ -1421,6 +1437,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ private int[] parseDimensionArray(TypedArray dimens) {
+ if (dimens == null) {
+ return null;
+ }
+ int[] result = new int[dimens.length()];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = dimens.getDimensionPixelSize(i, 0);
+ }
+ return result;
+ }
+
/**
* @hide
*/
@@ -3019,6 +3046,51 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
+ * pixels. For lines past the last element in the array, the last element repeats.
+ *
+ * @param leftIndents array of indent values for left margin, in pixels
+ * @param rightIndents array of indent values for right margin, in pixels
+ *
+ * @see #getLeftIndents()
+ * @see #getRightIndents()
+ *
+ * @attr ref android.R.styleable#TextView_leftIndents
+ * @attr ref android.R.styleable#TextView_rightIndents
+ */
+ public void setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
+ mLeftIndents = leftIndents;
+ mRightIndents = rightIndents;
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
+ }
+
+ /**
+ * Get left indents. See {#link setMargins} for more details.
+ *
+ * @return left indents
+ * @see #setIndents(int[], int[])
+ * @attr ref android.R.styleable#TextView_leftIndents
+ */
+ public int[] getLeftIndents() {
+ return mLeftIndents;
+ }
+
+ /**
+ * Get right indents. See {#link setMargins} for more details.
+ *
+ * @return right indents
+ * @see #setIndents(int[], int[])
+ * @attr ref android.R.styleable#TextView_rightIndents
+ */
+ public int[] getRightIndents() {
+ return mRightIndents;
+ }
+
+ /**
* Sets font feature settings. The format is the same as the CSS
* font-feature-settings attribute:
* http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
@@ -6564,6 +6636,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setSpacingAdd(mSpacingAdd)
.setIncludePad(mIncludePad)
.setBreakStrategy(mBreakStrategy);
+ if (mLeftIndents != null || mRightIndents != null) {
+ builder.setIndents(mLeftIndents, mRightIndents);
+ }
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth)
@@ -6652,6 +6727,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setSpacingAdd(mSpacingAdd)
.setIncludePad(mIncludePad)
.setBreakStrategy(mBreakStrategy);
+ if (mLeftIndents != null || mRightIndents != null) {
+ builder.setIndents(mLeftIndents, mRightIndents);
+ }
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth)
@@ -9124,6 +9202,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
void replaceSelectionWithText(CharSequence text) {
((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
+ mEditor.startSelectionActionModeWithSelection();
}
/**
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 2219ad10c720..0b1e0e5bcf6d 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -203,8 +203,8 @@ public final class FloatingToolbar {
if (mContentRect.top > mPopup.getHeight()) {
y = mContentRect.top - mPopup.getHeight();
mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_UP;
- } else if (mContentRect.top > getEstimatedToolbarHeight(mContext)) {
- y = mContentRect.top - getEstimatedToolbarHeight(mContext);
+ } else if (mContentRect.top > mPopup.getToolbarHeightWithVerticalMargin()) {
+ y = mContentRect.top - mPopup.getToolbarHeightWithVerticalMargin();
mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
} else {
y = mContentRect.bottom;
@@ -264,7 +264,8 @@ public final class FloatingToolbar {
private final View mParent;
private final PopupWindow mPopupWindow;
private final ViewGroup mContentContainer;
- private final int mPadding;
+ private final int mMarginHorizontal;
+ private final int mMarginVertical;
private final Animation.AnimationListener mOnOverflowOpened =
new Animation.AnimationListener() {
@@ -365,7 +366,10 @@ public final class FloatingToolbar {
.TOUCHABLE_INSETS_REGION);
}
});
- mPadding = parent.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_margin);
+ mMarginHorizontal = parent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
+ mMarginVertical = parent.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
}
/**
@@ -474,6 +478,10 @@ public final class FloatingToolbar {
return mContentContainer.getContext();
}
+ int getToolbarHeightWithVerticalMargin() {
+ return getEstimatedToolbarHeight(mParent.getContext()) + mMarginVertical * 2;
+ }
+
/**
* Performs the "grow and fade in from the bottom" animation on the floating popup.
*/
@@ -506,7 +514,7 @@ public final class FloatingToolbar {
mMainPanel.fadeOut(true);
Size overflowPanelSize = mOverflowPanel.measure();
- final int targetWidth = getOverflowWidth(mParent.getContext());
+ final int targetWidth = overflowPanelSize.getWidth();
final int targetHeight = overflowPanelSize.getHeight();
final boolean morphUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
final int startWidth = mContentContainer.getWidth();
@@ -624,10 +632,14 @@ public final class FloatingToolbar {
// Make sure the main panel is at the correct position.
if (mContentContainer.getChildAt(0) == mMainPanel.getView()) {
- mContentContainer.setX(mPadding);
- float y = mPadding;
+ float x = mPopupWindow.getWidth()
+ - (mMainPanel.getView().getMeasuredWidth() + mMarginHorizontal);
+ mContentContainer.setX(x);
+
+ float y = mMarginVertical;
if (mOverflowDirection == OVERFLOW_DIRECTION_UP) {
- y = getHeight() - getEstimatedToolbarHeight(mParent.getContext()) - mPadding;
+ y = getHeight()
+ - (mMainPanel.getView().getMeasuredHeight() + mMarginVertical);
}
mContentContainer.setY(y);
}
@@ -661,8 +673,8 @@ public final class FloatingToolbar {
width = Math.max(width, overflowPanelSize.getWidth());
height = Math.max(height, overflowPanelSize.getHeight());
}
- mPopupWindow.setWidth(width + mPadding * 2);
- mPopupWindow.setHeight(height + mPadding * 2);
+ mPopupWindow.setWidth(width + mMarginHorizontal * 2);
+ mPopupWindow.setHeight(height + mMarginVertical * 2);
}
/**
@@ -748,22 +760,22 @@ public final class FloatingToolbar {
final MenuItem menuItem = remainingMenuItems.peek();
Button menuItemButton = createMenuItemButton(mContext, menuItem);
- // Adding additional left padding for the first button to even out button spacing.
+ // Adding additional start padding for the first button to even out button spacing.
if (isFirstItem) {
- menuItemButton.setPadding(
- 2 * menuItemButton.getPaddingLeft(),
+ menuItemButton.setPaddingRelative(
+ (int) (1.5 * menuItemButton.getPaddingStart()),
menuItemButton.getPaddingTop(),
- menuItemButton.getPaddingRight(),
+ menuItemButton.getPaddingEnd(),
menuItemButton.getPaddingBottom());
isFirstItem = false;
}
- // Adding additional right padding for the last button to even out button spacing.
+ // Adding additional end padding for the last button to even out button spacing.
if (remainingMenuItems.size() == 1) {
- menuItemButton.setPadding(
- menuItemButton.getPaddingLeft(),
+ menuItemButton.setPaddingRelative(
+ menuItemButton.getPaddingStart(),
menuItemButton.getPaddingTop(),
- 2 * menuItemButton.getPaddingRight(),
+ (int) (1.5 * menuItemButton.getPaddingEnd()),
menuItemButton.getPaddingBottom());
}
@@ -836,10 +848,12 @@ public final class FloatingToolbar {
private final ViewGroup mBackButtonContainer;
private final View mBackButton;
private final ListView mListView;
+ private final TextView mListViewItemWidthCalculator;
private final ViewFader mViewFader;
private final Runnable mCloseOverflow;
private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+ private int mOverflowWidth = 0;
/**
* Initializes a floating toolbar popup overflow view panel.
@@ -865,7 +879,7 @@ public final class FloatingToolbar {
mBackButtonContainer = new LinearLayout(context);
mBackButtonContainer.addView(mBackButton);
- mListView = createOverflowListView(context);
+ mListView = createOverflowListView();
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@@ -878,6 +892,10 @@ public final class FloatingToolbar {
mContentView.addView(mListView);
mContentView.addView(mBackButtonContainer);
+
+ mListViewItemWidthCalculator = createOverflowMenuItemButton(context);
+ mListViewItemWidthCalculator.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
/**
@@ -888,6 +906,7 @@ public final class FloatingToolbar {
overflowListViewAdapter.clear();
overflowListViewAdapter.addAll(menuItems);
setListViewHeight();
+ setOverflowWidth();
}
public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
@@ -943,7 +962,21 @@ public final class FloatingToolbar {
mListView.setLayoutParams(params);
}
- private static ListView createOverflowListView(final Context context) {
+ private int setOverflowWidth() {
+ for (int i = 0; i < mListView.getAdapter().getCount(); i++) {
+ MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(i);
+ Preconditions.checkNotNull(menuItem);
+ mListViewItemWidthCalculator.setText(menuItem.getTitle());
+ mListViewItemWidthCalculator.measure(
+ MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ mOverflowWidth = Math.max(
+ mListViewItemWidthCalculator.getMeasuredWidth(), mOverflowWidth);
+ }
+ return mOverflowWidth;
+ }
+
+ private ListView createOverflowListView() {
+ final Context context = mContentView.getContext();
final ListView overflowListView = new ListView(context);
overflowListView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
@@ -962,6 +995,7 @@ public final class FloatingToolbar {
MenuItem menuItem = getItem(position);
menuButton.setText(menuItem.getTitle());
menuButton.setContentDescription(menuItem.getTitle());
+ menuButton.setMinimumWidth(mOverflowWidth);
return menuButton;
}
};
@@ -1077,11 +1111,6 @@ public final class FloatingToolbar {
return shrinkFadeOutFromBottomAnimation;
}
- private static int getOverflowWidth(Context context) {
- return context.getResources()
- .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_width);
- }
-
private static int getEstimatedToolbarHeight(Context context) {
return context.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_height);
}
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 3f8bfe291013..348b0ec083b7 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -64,7 +64,7 @@ public:
return JNI_FALSE;
}
- static jlong validateNinePatchChunk(JNIEnv* env, jobject, jlong, jbyteArray obj) {
+ static jlong validateNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) {
size_t chunkSize = env->GetArrayLength(obj);
if (chunkSize < (int) (sizeof(Res_png_9patch))) {
jniThrowRuntimeException(env, "Array too small for chunk.");
@@ -88,13 +88,13 @@ public:
}
}
- static void draw(JNIEnv* env, SkCanvas* canvas, SkRect& bounds, const SkBitmap* bitmap,
+ static void draw(JNIEnv* env, SkCanvas* canvas, SkRect& bounds, const SkBitmap& bitmap,
Res_png_9patch* chunk, const SkPaint* paint, jint destDensity, jint srcDensity) {
if (destDensity == srcDensity || destDensity == 0 || srcDensity == 0) {
ALOGV("Drawing unscaled 9-patch: (%g,%g)-(%g,%g)",
SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom));
- NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
+ NinePatch_Draw(canvas, bounds, bitmap, *chunk, paint, NULL);
} else {
canvas->save();
@@ -111,25 +111,25 @@ public:
SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom),
srcDensity, destDensity);
- NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
+ NinePatch_Draw(canvas, bounds, bitmap, *chunk, paint, NULL);
canvas->restore();
}
}
static void drawF(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRectF,
- jlong bitmapHandle, jlong chunkHandle, jlong paintHandle,
+ jobject jbitmap, jlong chunkHandle, jlong paintHandle,
jint destDensity, jint srcDensity) {
SkCanvas* canvas = reinterpret_cast<Canvas*>(canvasHandle)->asSkCanvas();
- const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SkASSERT(canvas);
SkASSERT(boundsRectF);
- SkASSERT(bitmap);
SkASSERT(chunk);
// paint is optional
+ SkBitmap bitmap;
+ GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
SkRect bounds;
GraphicsJNI::jrectf_to_rect(env, boundsRectF, &bounds);
@@ -137,36 +137,36 @@ public:
}
static void drawI(JNIEnv* env, jobject, jlong canvasHandle, jobject boundsRect,
- jlong bitmapHandle, jlong chunkHandle, jlong paintHandle,
+ jobject jbitmap, jlong chunkHandle, jlong paintHandle,
jint destDensity, jint srcDensity) {
SkCanvas* canvas = reinterpret_cast<Canvas*>(canvasHandle)->asSkCanvas();
- const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
SkASSERT(canvas);
SkASSERT(boundsRect);
- SkASSERT(bitmap);
SkASSERT(chunk);
// paint is optional
+ SkBitmap bitmap;
+ GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
SkRect bounds;
GraphicsJNI::jrect_to_rect(env, boundsRect, &bounds);
draw(env, canvas, bounds, bitmap, chunk, paint, destDensity, srcDensity);
}
- static jlong getTransparentRegion(JNIEnv* env, jobject, jlong bitmapHandle,
+ static jlong getTransparentRegion(JNIEnv* env, jobject, jobject jbitmap,
jlong chunkHandle, jobject boundsRect) {
- const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
Res_png_9patch* chunk = reinterpret_cast<Res_png_9patch*>(chunkHandle);
- SkASSERT(bitmap);
SkASSERT(chunk);
SkASSERT(boundsRect);
+ SkBitmap bitmap;
+ GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
SkRect bounds;
GraphicsJNI::jrect_to_rect(env, boundsRect, &bounds);
SkRegion* region = NULL;
- NinePatch_Draw(NULL, bounds, *bitmap, *chunk, NULL, &region);
+ NinePatch_Draw(NULL, bounds, bitmap, *chunk, NULL, &region);
return reinterpret_cast<jlong>(region);
}
@@ -176,13 +176,16 @@ public:
/////////////////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gNinePatchMethods[] = {
- { "isNinePatchChunk", "([B)Z", (void*) SkNinePatchGlue::isNinePatchChunk },
- { "validateNinePatchChunk", "(J[B)J", (void*) SkNinePatchGlue::validateNinePatchChunk },
- { "nativeFinalize", "(J)V", (void*) SkNinePatchGlue::finalize },
- { "nativeDraw", "(JLandroid/graphics/RectF;JJJII)V", (void*) SkNinePatchGlue::drawF },
- { "nativeDraw", "(JLandroid/graphics/Rect;JJJII)V", (void*) SkNinePatchGlue::drawI },
- { "nativeGetTransparentRegion", "(JJLandroid/graphics/Rect;)J",
- (void*) SkNinePatchGlue::getTransparentRegion }
+ { "isNinePatchChunk", "([B)Z", (void*) SkNinePatchGlue::isNinePatchChunk },
+ { "validateNinePatchChunk", "([B)J",
+ (void*) SkNinePatchGlue::validateNinePatchChunk },
+ { "nativeFinalize", "(J)V", (void*) SkNinePatchGlue::finalize },
+ { "nativeDraw", "(JLandroid/graphics/RectF;Landroid/graphics/Bitmap;JJII)V",
+ (void*) SkNinePatchGlue::drawF },
+ { "nativeDraw", "(JLandroid/graphics/Rect;Landroid/graphics/Bitmap;JJII)V",
+ (void*) SkNinePatchGlue::drawI },
+ { "nativeGetTransparentRegion", "(Landroid/graphics/Bitmap;JLandroid/graphics/Rect;)J",
+ (void*) SkNinePatchGlue::getTransparentRegion }
};
int register_android_graphics_NinePatch(JNIEnv* env) {
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 50a106917ee8..9b5fb3a60119 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -318,11 +318,12 @@ static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle,
indices, indexCount, *paint);
}
-static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jlong bitmapHandle,
+static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jobject jbitmap,
jfloat left, jfloat top, jlong paintHandle, jint canvasDensity,
jint screenDensity, jint bitmapDensity) {
Canvas* canvas = get_canvas(canvasHandle);
- const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
+ SkBitmap bitmap;
+ GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
if (canvasDensity == bitmapDensity || canvasDensity == 0 || bitmapDensity == 0) {
@@ -332,9 +333,9 @@ static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jlong b
filteredPaint = *paint;
}
filteredPaint.setFilterQuality(kLow_SkFilterQuality);
- canvas->drawBitmap(*bitmap, left, top, &filteredPaint);
+ canvas->drawBitmap(bitmap, left, top, &filteredPaint);
} else {
- canvas->drawBitmap(*bitmap, left, top, paint);
+ canvas->drawBitmap(bitmap, left, top, paint);
}
} else {
canvas->save(SkCanvas::kMatrixClip_SaveFlag);
@@ -348,37 +349,39 @@ static void drawBitmap(JNIEnv* env, jobject jcanvas, jlong canvasHandle, jlong b
}
filteredPaint.setFilterQuality(kLow_SkFilterQuality);
- canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint);
+ canvas->drawBitmap(bitmap, 0, 0, &filteredPaint);
canvas->restore();
}
}
-static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+static void drawBitmapMatrix(JNIEnv* env, jobject, jlong canvasHandle, jobject jbitmap,
jlong matrixHandle, jlong paintHandle) {
- const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- get_canvas(canvasHandle)->drawBitmap(*bitmap, *matrix, paint);
+ SkBitmap bitmap;
+ GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
+ get_canvas(canvasHandle)->drawBitmap(bitmap, *matrix, paint);
}
-static void drawBitmapRect(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+static void drawBitmapRect(JNIEnv* env, jobject, jlong canvasHandle, jobject jbitmap,
float srcLeft, float srcTop, float srcRight, float srcBottom,
float dstLeft, float dstTop, float dstRight, float dstBottom,
jlong paintHandle, jint screenDensity, jint bitmapDensity) {
Canvas* canvas = get_canvas(canvasHandle);
- const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ SkBitmap bitmap;
+ GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
if (screenDensity != 0 && screenDensity != bitmapDensity) {
Paint filteredPaint;
if (paint) {
filteredPaint = *paint;
}
filteredPaint.setFilterQuality(kLow_SkFilterQuality);
- canvas->drawBitmap(*bitmap, srcLeft, srcTop, srcRight, srcBottom,
+ canvas->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom,
dstLeft, dstTop, dstRight, dstBottom, &filteredPaint);
} else {
- canvas->drawBitmap(*bitmap, srcLeft, srcTop, srcRight, srcBottom,
+ canvas->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom,
dstLeft, dstTop, dstRight, dstBottom, paint);
}
}
@@ -406,16 +409,17 @@ static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle,
get_canvas(canvasHandle)->drawBitmap(bitmap, x, y, paint);
}
-static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle,
+static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jobject jbitmap,
jint meshWidth, jint meshHeight, jfloatArray jverts,
jint vertIndex, jintArray jcolors, jint colorIndex, jlong paintHandle) {
const int ptCount = (meshWidth + 1) * (meshHeight + 1);
AutoJavaFloatArray vertA(env, jverts, vertIndex + (ptCount << 1));
AutoJavaIntArray colorA(env, jcolors, colorIndex + ptCount);
- const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
- get_canvas(canvasHandle)->drawBitmapMesh(*bitmap, meshWidth, meshHeight,
+ SkBitmap bitmap;
+ GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
+ get_canvas(canvasHandle)->drawBitmapMesh(bitmap, meshWidth, meshHeight,
vertA.ptr(), colorA.ptr(), paint);
}
@@ -700,11 +704,11 @@ static JNINativeMethod gMethods[] = {
{"native_drawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
{"native_drawPath","(JJJ)V", (void*) CanvasJNI::drawPath},
{"nativeDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
- {"native_drawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap},
- {"nativeDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
- {"native_drawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
+ {"native_drawBitmap","(JLandroid/graphics/Bitmap;FFJIII)V", (void*) CanvasJNI::drawBitmap},
+ {"nativeDrawBitmapMatrix", "(JLandroid/graphics/Bitmap;JJ)V", (void*)CanvasJNI::drawBitmapMatrix},
+ {"native_drawBitmap","(JLandroid/graphics/Bitmap;FFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
{"native_drawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
- {"nativeDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
+ {"nativeDrawBitmapMesh", "(JLandroid/graphics/Bitmap;II[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
{"native_drawText","(J[CIIFFIJJ)V", (void*) CanvasJNI::drawTextChars},
{"native_drawText","(JLjava/lang/String;IIFFIJJ)V", (void*) CanvasJNI::drawTextString},
{"native_drawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 87c58d62dee8..5e73ef250435 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -132,6 +132,13 @@ static void nSetLocale(JNIEnv* env, jclass, jlong nativePtr, jstring javaLocaleN
}
}
+static void nSetIndents(JNIEnv* env, jclass, jlong nativePtr, jintArray indents) {
+ ScopedIntArrayRO indentArr(env, indents);
+ std::vector<float> indentVec(indentArr.get(), indentArr.get() + indentArr.size());
+ LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
+ b->setIndents(indentVec);
+}
+
// Basically similar to Paint.getTextRunAdvances but with C++ interface
static jfloat nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr,
jlong nativePaint, jlong nativeTypeface, jint start, jint end, jboolean isRtl) {
@@ -171,6 +178,7 @@ static JNINativeMethod gMethods[] = {
{"nLoadHyphenator", "(Ljava/lang/String;)J", (void*) nLoadHyphenator},
{"nSetLocale", "(JLjava/lang/String;J)V", (void*) nSetLocale},
{"nSetupParagraph", "(J[CIFIF[III)V", (void*) nSetupParagraph},
+ {"nSetIndents", "(J[I)V", (void*) nSetIndents},
{"nAddStyleRun", "(JJJIIZ)F", (void*) nAddStyleRun},
{"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun},
{"nAddReplacementRun", "(JIIF)V", (void*) nAddReplacementRun},
diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp
index f2e6c4b5a8fd..f42c89c12aa8 100644
--- a/core/jni/android_view_DisplayListCanvas.cpp
+++ b/core/jni/android_view_DisplayListCanvas.cpp
@@ -133,10 +133,10 @@ static jint android_view_DisplayListCanvas_getMaxTextureHeight(JNIEnv* env, jobj
// ----------------------------------------------------------------------------
static void android_view_DisplayListCanvas_drawPatch(JNIEnv* env, jobject clazz,
- jlong rendererPtr, jlong bitmapPtr, jlong patchPtr,
+ jlong rendererPtr, jobject jbitmap, jlong patchPtr,
float left, float top, float right, float bottom, jlong paintPtr) {
- SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapPtr);
-
+ SkBitmap bitmap;
+ GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(patchPtr);
Paint* paint = reinterpret_cast<Paint*>(paintPtr);
@@ -276,7 +276,7 @@ static JNINativeMethod gMethods[] = {
{ "nCallDrawGLFunction", "(JJ)V", (void*) android_view_DisplayListCanvas_callDrawGLFunction },
- { "nDrawPatch", "(JJJFFFFJ)V", (void*) android_view_DisplayListCanvas_drawPatch },
+ { "nDrawPatch", "(JLandroid/graphics/Bitmap;JFFFFJ)V", (void*) android_view_DisplayListCanvas_drawPatch },
{ "nDrawRects", "(JJJ)V", (void*) android_view_DisplayListCanvas_drawRegionAsRects },
{ "nDrawRoundRect", "(JJJJJJJJ)V", (void*) android_view_DisplayListCanvas_drawRoundRectProps },
diff --git a/core/res/res/drawable/ic_audio_media.xml b/core/res/res/drawable/ic_audio_media.xml
new file mode 100644
index 000000000000..a453b3db6434
--- /dev/null
+++ b/core/res/res/drawable/ic_audio_media.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="32.0dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:width="32.0dp" >
+
+ <path
+ android:fillColor="?android:attr/colorControlNormal"
+ android:pathData="M12.0,3.0l0.0,9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12.0 6.0,14.01 6.0,16.5S8.01,21.0 10.5,21.0c2.31,0.0 4.2,-1.75 4.45,-4.0L15.0,17.0L15.0,6.0l4.0,0.0L19.0,3.0l-7.0,0.0z" />
+
+</vector>
+
diff --git a/core/res/res/drawable/ic_audio_media_mute.xml b/core/res/res/drawable/ic_audio_media_mute.xml
new file mode 100644
index 000000000000..2e7f6dc7b8ba
--- /dev/null
+++ b/core/res/res/drawable/ic_audio_media_mute.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="32.0dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:width="32.0dp" >
+
+ <path
+ android:fillColor="?android:attr/colorControlNormal"
+ android:pathData="M15.0,6.0l4.0,0.0L19.0,3.0l-7.0,0.0l0.0,5.6l3.0,3.0C15.0,8.8 15.0,6.0 15.0,6.0z" />
+ <path
+ android:fillColor="?android:attr/colorControlNormal"
+ android:pathData="M4.8,3.9L3.5,5.1l6.9,6.9C8.0,12.1 6.0,14.0 6.0,16.5C6.0,19.0 8.0,21.0 10.5,21.0c2.7,0.0 4.5,-2.3 4.5,-4.3c0.0,0.0 0.0,-0.1 0.0,-0.1l4.0,4.0l1.3,-1.3L4.8,3.9z" />
+
+</vector>
+
diff --git a/core/res/res/layout/floating_popup_container.xml b/core/res/res/layout/floating_popup_container.xml
index f24791973182..e1af94c29d48 100644
--- a/core/res/res/layout/floating_popup_container.xml
+++ b/core/res/res/layout/floating_popup_container.xml
@@ -19,7 +19,9 @@
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="@dimen/floating_toolbar_height"
+ android:padding="0dp"
+ android:layout_margin="0dp"
android:elevation="2dp"
android:focusable="true"
android:focusableInTouchMode="true"
- android:background="@android:color/background_light" />
+ android:background="@color/floating_toolbar_background_color"/>
diff --git a/core/res/res/layout/floating_popup_menu_button.xml b/core/res/res/layout/floating_popup_menu_button.xml
index 9fa13bdeb94e..70227fa36911 100644
--- a/core/res/res/layout/floating_popup_menu_button.xml
+++ b/core/res/res/layout/floating_popup_menu_button.xml
@@ -19,13 +19,15 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="@dimen/floating_toolbar_menu_button_side_padding"
- android:paddingLeft="@dimen/floating_toolbar_menu_button_side_padding"
- android:paddingRight="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingStart="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingEnd="@dimen/floating_toolbar_menu_button_side_padding"
android:paddingTop="0dp"
android:paddingBottom="0dp"
+ android:layout_margin="0dp"
android:singleLine="true"
android:ellipsize="end"
android:fontFamily="sans-serif"
android:textSize="@dimen/floating_toolbar_text_size"
android:textAllCaps="true"
- android:background="?attr/selectableItemBackground" /> \ No newline at end of file
+ android:textColor="@color/floating_toolbar_text_color"
+ android:background="?attr/selectableItemBackground" />
diff --git a/core/res/res/layout/floating_popup_overflow_list_item b/core/res/res/layout/floating_popup_overflow_list_item
index 9294f3b1ab4b..c0db1bd64755 100644
--- a/core/res/res/layout/floating_popup_overflow_list_item
+++ b/core/res/res/layout/floating_popup_overflow_list_item
@@ -22,12 +22,14 @@
android:gravity="center_vertical"
android:minWidth="@dimen/floating_toolbar_menu_button_side_padding"
android:minHeight="@dimen/floating_toolbar_height"
- android:paddingLeft="@dimen/floating_toolbar_menu_button_side_padding"
- android:paddingRight="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingStart="@dimen/floating_toolbar_overflow_side_padding"
+ android:paddingEnd="@dimen/floating_toolbar_overflow_side_padding"
android:paddingTop="0dp"
android:paddingBottom="0dp"
+ android:layout_margin="0dp"
android:singleLine="true"
android:ellipsize="end"
android:fontFamily="sans-serif"
android:textSize="@dimen/floating_toolbar_text_size"
+ android:textColor="@color/floating_toolbar_text_color"
android:textAllCaps="true" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a623d0cb5f16..674c695a6776 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4322,6 +4322,10 @@
<!-- Line breaking stratgegy balances line lengths. -->
<enum name="balanced" value="2" />
</attr>
+ <!-- Array of indents, one dimension value per line, left side. -->
+ <attr name="leftIndents" format="reference" />
+ <!-- Array of indents, one dimension value per line, right side. -->
+ <attr name="rightIndents" format="reference" />
</declare-styleable>
<declare-styleable name="TextViewAppearance">
<!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index b9825c534705..f1d22424731a 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -174,4 +174,8 @@
<color name="Pink_800">#ffad1457</color>
<color name="Red_700">#ffc53929</color>
<color name="Red_800">#ffb93221</color>
+
+ <!-- Floating toolbar colors -->
+ <color name="floating_toolbar_text_color">#DD000000</color>
+ <color name="floating_toolbar_background_color">#FAFAFA</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8c78e74fe166..83360584d21c 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1962,8 +1962,10 @@
string that's stored in 8-bit unpacked format) characters.-->
<bool translatable="false" name="config_sms_decode_gsm_8bit_data">false</bool>
- <!-- Package name providing WebView implementation. -->
- <string name="config_webViewPackageName" translatable="false">com.android.webview</string>
+ <!-- List of package names (ordered by preference) providing WebView implementations. -->
+ <string-array name="config_webViewPackageNames" translatable="false">
+ <item>com.android.webview</item>
+ </string-array>
<!-- If EMS is not supported, framework breaks down EMS into single segment SMS
and adds page info " x/y". This config is used to set which carrier doesn't
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 100b1610ae5f..bbba712c81a1 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -387,12 +387,14 @@
<!-- Floating toolbar dimensions -->
<dimen name="floating_toolbar_height">48dp</dimen>
- <dimen name="floating_toolbar_menu_button_side_padding">8dp</dimen>
+ <dimen name="floating_toolbar_menu_button_side_padding">16dp</dimen>
+ <dimen name="floating_toolbar_overflow_side_padding">18dp</dimen>
<dimen name="floating_toolbar_text_size">14sp</dimen>
<dimen name="floating_toolbar_menu_button_minimum_width">48dp</dimen>
- <dimen name="floating_toolbar_default_width">250dp</dimen>
- <dimen name="floating_toolbar_minimum_overflow_height">192dp</dimen>
- <dimen name="floating_toolbar_overflow_width">130dp</dimen>
- <dimen name="floating_toolbar_margin">2dp</dimen>
+ <dimen name="floating_toolbar_default_width">264dp</dimen>
+ <dimen name="floating_toolbar_minimum_overflow_height">144dp</dimen>
+ <dimen name="floating_toolbar_horizontal_margin">16dp</dimen>
+ <dimen name="floating_toolbar_vertical_margin">8dp</dimen>
+
<dimen name="chooser_grid_padding">0dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 880686946282..282c80b9e50b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2665,4 +2665,7 @@
<public type="attr" name="durationScaleHint" />
<public type="attr" name="lockTaskMode" />
+
+ <public type="attr" name="leftIndents" />
+ <public type="attr" name="rightIndents" />
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e5b1cb5bc169..ac8216e81bc7 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1165,6 +1165,8 @@
<java-symbol type="drawable" name="ic_audio_alarm_mute" />
<java-symbol type="drawable" name="ic_audio_bt" />
<java-symbol type="drawable" name="ic_audio_bt_mute" />
+ <java-symbol type="drawable" name="ic_audio_media" />
+ <java-symbol type="drawable" name="ic_audio_media_mute" />
<java-symbol type="drawable" name="ic_audio_notification" />
<java-symbol type="drawable" name="ic_audio_notification_mute" />
<java-symbol type="drawable" name="ic_audio_phone" />
@@ -1995,7 +1997,7 @@
<java-symbol type="attr" name="actionModeWebSearchDrawable" />
<java-symbol type="string" name="websearch" />
<java-symbol type="drawable" name="ic_media_video_poster" />
- <java-symbol type="string" name="config_webViewPackageName" />
+ <java-symbol type="array" name="config_webViewPackageNames" />
<!-- From SubtitleView -->
<java-symbol type="dimen" name="subtitle_corner_radius" />
@@ -2223,12 +2225,13 @@
<java-symbol type="layout" name="floating_popup_overflow_list_item" />
<java-symbol type="dimen" name="floating_toolbar_height" />
<java-symbol type="dimen" name="floating_toolbar_menu_button_side_padding" />
+ <java-symbol type="dimen" name="floating_toolbar_overflow_side_padding" />
<java-symbol type="dimen" name="floating_toolbar_text_size" />
<java-symbol type="dimen" name="floating_toolbar_menu_button_minimum_width" />
<java-symbol type="dimen" name="floating_toolbar_default_width" />
<java-symbol type="dimen" name="floating_toolbar_minimum_overflow_height" />
- <java-symbol type="dimen" name="floating_toolbar_overflow_width" />
- <java-symbol type="dimen" name="floating_toolbar_margin" />
+ <java-symbol type="dimen" name="floating_toolbar_horizontal_margin" />
+ <java-symbol type="dimen" name="floating_toolbar_vertical_margin" />
<java-symbol type="drawable" name="ic_chevron_left" />
<java-symbol type="drawable" name="ic_chevron_right" />
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 76d6edfdd579..be5c52b23fb5 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1568,11 +1568,6 @@ public final class Bitmap implements Parcelable {
nativePrepareToDraw(mSkBitmapPtr);
}
- /** @hide */
- public final long getSkBitmap() {
- return mSkBitmapPtr;
- }
-
/**
* Refs the underlying SkPixelRef and returns a pointer to it.
*
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 2acb8baaaa06..1c568844f5a5 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1335,7 +1335,7 @@ public class Canvas {
*/
public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
throwIfCannotDraw(bitmap);
- native_drawBitmap(mNativeCanvasWrapper, bitmap.getSkBitmap(), left, top,
+ native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top,
paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity, bitmap.mDensity);
}
@@ -1381,7 +1381,7 @@ public class Canvas {
bottom = src.bottom;
}
- native_drawBitmap(mNativeCanvasWrapper, bitmap.getSkBitmap(), left, top, right, bottom,
+ native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
bitmap.mDensity);
}
@@ -1428,7 +1428,7 @@ public class Canvas {
bottom = src.bottom;
}
- native_drawBitmap(mNativeCanvasWrapper, bitmap.getSkBitmap(), left, top, right, bottom,
+ native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top, right, bottom,
dst.left, dst.top, dst.right, dst.bottom, nativePaint, mScreenDensity,
bitmap.mDensity);
}
@@ -1509,7 +1509,7 @@ public class Canvas {
* @param paint May be null. The paint used to draw the bitmap
*/
public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
- nativeDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getSkBitmap(), matrix.ni(),
+ nativeDrawBitmapMatrix(mNativeCanvasWrapper, bitmap, matrix.ni(),
paint != null ? paint.getNativeInstance() : 0);
}
@@ -1564,7 +1564,7 @@ public class Canvas {
// no mul by 2, since we need only 1 color per vertex
checkRange(colors.length, colorOffset, count);
}
- nativeDrawBitmapMesh(mNativeCanvasWrapper, bitmap.getSkBitmap(), meshWidth, meshHeight,
+ nativeDrawBitmapMesh(mNativeCanvasWrapper, bitmap, meshWidth, meshHeight,
verts, vertOffset, colors, colorOffset,
paint != null ? paint.getNativeInstance() : 0);
}
@@ -2052,13 +2052,13 @@ public class Canvas {
private static native void native_drawPath(long nativeCanvas,
long nativePath,
long nativePaint);
- private native void native_drawBitmap(long nativeCanvas, long nativeBitmap,
+ private native void native_drawBitmap(long nativeCanvas, Bitmap bitmap,
float left, float top,
long nativePaintOrZero,
int canvasDensity,
int screenDensity,
int bitmapDensity);
- private native void native_drawBitmap(long nativeCanvas, long nativeBitmap,
+ private native void native_drawBitmap(long nativeCanvas, Bitmap bitmap,
float srcLeft, float srcTop, float srcRight, float srcBottom,
float dstLeft, float dstTop, float dstRight, float dstBottom,
long nativePaintOrZero, int screenDensity, int bitmapDensity);
@@ -2068,11 +2068,11 @@ public class Canvas {
boolean hasAlpha,
long nativePaintOrZero);
private static native void nativeDrawBitmapMatrix(long nativeCanvas,
- long nativeBitmap,
+ Bitmap bitmap,
long nativeMatrix,
long nativePaint);
private static native void nativeDrawBitmapMesh(long nativeCanvas,
- long nativeBitmap,
+ Bitmap bitmap,
int meshWidth, int meshHeight,
float[] verts, int vertOffset,
int[] colors, int colorOffset,
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index 9c4299aa5d70..21a212a0ec42 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -98,7 +98,7 @@ public class NinePatch {
public NinePatch(Bitmap bitmap, byte[] chunk, String srcName) {
mBitmap = bitmap;
mSrcName = srcName;
- mNativeChunk = validateNinePatchChunk(mBitmap.getSkBitmap(), chunk);
+ mNativeChunk = validateNinePatchChunk(chunk);
}
/**
@@ -199,12 +199,12 @@ public class NinePatch {
}
void drawSoftware(Canvas canvas, RectF location, Paint paint) {
- nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.getSkBitmap(), mNativeChunk,
+ nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap, mNativeChunk,
paint != null ? paint.getNativeInstance() : 0, canvas.mDensity, mBitmap.mDensity);
}
void drawSoftware(Canvas canvas, Rect location, Paint paint) {
- nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap.getSkBitmap(), mNativeChunk,
+ nativeDraw(canvas.getNativeCanvasWrapper(), location, mBitmap, mNativeChunk,
paint != null ? paint.getNativeInstance() : 0, canvas.mDensity, mBitmap.mDensity);
}
@@ -252,7 +252,7 @@ public class NinePatch {
* that are transparent.
*/
public final Region getTransparentRegion(Rect bounds) {
- long r = nativeGetTransparentRegion(mBitmap.getSkBitmap(), mNativeChunk, bounds);
+ long r = nativeGetTransparentRegion(mBitmap, mNativeChunk, bounds);
return r != 0 ? new Region(r) : null;
}
@@ -271,11 +271,11 @@ public class NinePatch {
* If validation is successful, this method returns a native Res_png_9patch*
* object used by the renderers.
*/
- private static native long validateNinePatchChunk(long bitmap, byte[] chunk);
+ private static native long validateNinePatchChunk(byte[] chunk);
private static native void nativeFinalize(long chunk);
- private static native void nativeDraw(long canvas_instance, RectF loc, long bitmap_instance,
+ private static native void nativeDraw(long canvas_instance, RectF loc, Bitmap bitmap_instance,
long c, long paint_instance_or_null, int destDensity, int srcDensity);
- private static native void nativeDraw(long canvas_instance, Rect loc, long bitmap_instance,
+ private static native void nativeDraw(long canvas_instance, Rect loc, Bitmap bitmap_instance,
long c, long paint_instance_or_null, int destDensity, int srcDensity);
- private static native long nativeGetTransparentRegion(long bitmap, long chunk, Rect location);
+ private static native long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location);
}
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 8757e15006b7..4d596fee5fef 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -220,7 +220,7 @@ void DisplayListRenderer::drawLayer(DeferredLayerUpdater* layerHandle, float x,
}
void DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
- bitmap = refBitmap(bitmap);
+ bitmap = refBitmap(*bitmap);
paint = refPaint(paint);
addDrawOp(new (alloc()) DrawBitmapOp(bitmap, paint));
@@ -286,7 +286,7 @@ void DisplayListRenderer::drawBitmap(const SkBitmap& bitmap, float srcLeft, floa
dstRight = srcRight - srcLeft;
dstBottom = srcBottom - srcTop;
- addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(&bitmap),
+ addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(bitmap),
srcLeft, srcTop, srcRight, srcBottom,
dstLeft, dstTop, dstRight, dstBottom, paint));
restore();
@@ -294,7 +294,7 @@ void DisplayListRenderer::drawBitmap(const SkBitmap& bitmap, float srcLeft, floa
}
}
- addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(&bitmap),
+ addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(bitmap),
srcLeft, srcTop, srcRight, srcBottom,
dstLeft, dstTop, dstRight, dstBottom, paint));
}
@@ -307,17 +307,17 @@ void DisplayListRenderer::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth,
paint = refPaint(paint);
colors = refBuffer<int>(colors, vertexCount); // 1 color per vertex
- addDrawOp(new (alloc()) DrawBitmapMeshOp(refBitmap(&bitmap), meshWidth, meshHeight,
+ addDrawOp(new (alloc()) DrawBitmapMeshOp(refBitmap(bitmap), meshWidth, meshHeight,
vertices, colors, paint));
}
-void DisplayListRenderer::drawPatch(const SkBitmap* bitmap, const Res_png_9patch* patch,
+void DisplayListRenderer::drawPatch(const SkBitmap& bitmap, const Res_png_9patch* patch,
float left, float top, float right, float bottom, const SkPaint* paint) {
- bitmap = refBitmap(bitmap);
+ const SkBitmap* bitmapPtr = refBitmap(bitmap);
patch = refPatch(patch);
paint = refPaint(paint);
- addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, paint));
+ addDrawOp(new (alloc()) DrawPatchOp(bitmapPtr, patch, left, top, right, bottom, paint));
}
void DisplayListRenderer::drawColor(int color, SkXfermode::Mode mode) {
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index ff698f59bcb1..44cf546244b3 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -100,7 +100,7 @@ public:
// Bitmap-based
void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
// TODO: move drawPatch() to Canvas.h
- void drawPatch(const SkBitmap* bitmap, const Res_png_9patch* patch,
+ void drawPatch(const SkBitmap& bitmap, const Res_png_9patch* patch,
float left, float top, float right, float bottom, const SkPaint* paint);
// Shapes
@@ -347,7 +347,7 @@ private:
return cachedRegion;
}
- inline const SkBitmap* refBitmap(const SkBitmap* bitmap) {
+ inline const SkBitmap* refBitmap(const SkBitmap& bitmap) {
// Note that this assumes the bitmap is immutable. There are cases this won't handle
// correctly, such as creating the bitmap from scratch, drawing with it, changing its
// contents, and drawing again. The only fix would be to always copy it the first time,
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index d3b8d706e606..454fedc2b179 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -59,13 +59,13 @@ void ResourceCache::unlock() {
mLock.unlock();
}
-const SkBitmap* ResourceCache::insert(const SkBitmap* bitmapResource) {
+const SkBitmap* ResourceCache::insert(const SkBitmap& bitmapResource) {
Mutex::Autolock _l(mLock);
BitmapKey bitmapKey(bitmapResource);
ssize_t index = mBitmapCache.indexOfKey(bitmapKey);
if (index == NAME_NOT_FOUND) {
- SkBitmap* cachedBitmap = new SkBitmap(*bitmapResource);
+ SkBitmap* cachedBitmap = new SkBitmap(bitmapResource);
index = mBitmapCache.add(bitmapKey, cachedBitmap);
return cachedBitmap;
}
@@ -121,7 +121,7 @@ void ResourceCache::decrementRefcountLocked(void* resource) {
}
void ResourceCache::decrementRefcountLocked(const SkBitmap* bitmapResource) {
- BitmapKey bitmapKey(bitmapResource);
+ BitmapKey bitmapKey(*bitmapResource);
ssize_t index = mBitmapCache.indexOfKey(bitmapKey);
LOG_ALWAYS_FATAL_IF(index == NAME_NOT_FOUND,
diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h
index fae55d14fead..6c483faf3508 100644
--- a/libs/hwui/ResourceCache.h
+++ b/libs/hwui/ResourceCache.h
@@ -53,11 +53,11 @@ public:
class BitmapKey {
public:
- BitmapKey(const SkBitmap* bitmap)
+ BitmapKey(const SkBitmap& bitmap)
: mRefCount(1)
- , mBitmapDimensions(bitmap->dimensions())
- , mPixelRefOrigin(bitmap->pixelRefOrigin())
- , mPixelRefStableID(bitmap->pixelRef()->getStableID()) { }
+ , mBitmapDimensions(bitmap.dimensions())
+ , mPixelRefOrigin(bitmap.pixelRefOrigin())
+ , mPixelRefStableID(bitmap.pixelRef()->getStableID()) { }
void operator=(const BitmapKey& other);
bool operator==(const BitmapKey& other) const;
@@ -101,7 +101,7 @@ public:
* The cache stores a copy of the provided resource or refs an existing resource
* if the bitmap has previously been inserted and returns the cached copy.
*/
- const SkBitmap* insert(const SkBitmap* resource);
+ const SkBitmap* insert(const SkBitmap& resource);
void incrementRefcount(const Res_png_9patch* resource);
diff --git a/media/jni/android_media_MediaSync.cpp b/media/jni/android_media_MediaSync.cpp
index b96c733f88f8..e167f836e844 100644
--- a/media/jni/android_media_MediaSync.cpp
+++ b/media/jni/android_media_MediaSync.cpp
@@ -71,8 +71,8 @@ status_t JMediaSync::createInputSurface(
return mSync->createInputSurface(bufferProducer);
}
-void JMediaSync::setPlaybackRate(float rate) {
- mSync->setPlaybackRate(rate);
+status_t JMediaSync::setPlaybackRate(float rate) {
+ return mSync->setPlaybackRate(rate);
}
sp<const MediaClock> JMediaSync::getMediaClock() {
@@ -115,15 +115,23 @@ static void android_media_MediaSync_release(JNIEnv *env, jobject thiz) {
static void throwExceptionAsNecessary(
JNIEnv *env, status_t err, const char *msg = NULL) {
switch (err) {
- case INVALID_OPERATION:
- jniThrowException(env, "java/lang/IllegalStateException", msg);
+ case NO_ERROR:
break;
case BAD_VALUE:
jniThrowException(env, "java/lang/IllegalArgumentException", msg);
break;
+ case NO_INIT:
+ case INVALID_OPERATION:
default:
+ if (err > 0) {
+ break;
+ }
+ AString msgWithErrorCode(msg);
+ msgWithErrorCode.append(" error:");
+ msgWithErrorCode.append(err);
+ jniThrowException(env, "java/lang/IllegalStateException", msgWithErrorCode.c_str());
break;
}
}
@@ -295,7 +303,11 @@ static void android_media_MediaSync_native_setPlaybackRate(
return;
}
- sync->setPlaybackRate(rate);
+ status_t err = sync->setPlaybackRate(rate);
+ if (err != NO_ERROR) {
+ throwExceptionAsNecessary(env, err);
+ return;
+ }
}
static void android_media_MediaSync_native_finalize(JNIEnv *env, jobject thiz) {
diff --git a/media/jni/android_media_MediaSync.h b/media/jni/android_media_MediaSync.h
index 976a456be6ff..9e5de7e88090 100644
--- a/media/jni/android_media_MediaSync.h
+++ b/media/jni/android_media_MediaSync.h
@@ -39,7 +39,7 @@ struct JMediaSync : public RefBase {
status_t updateQueuedAudioData(int sizeInBytes, int64_t presentationTimeUs);
- void setPlaybackRate(float rate);
+ status_t setPlaybackRate(float rate);
sp<const MediaClock> getMediaClock();
diff --git a/obex/Android.mk b/obex/Android.mk
index fbfe9bead7e9..e7c1fd34dd74 100644
--- a/obex/Android.mk
+++ b/obex/Android.mk
@@ -7,3 +7,14 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE:= javax.obex
include $(BUILD_JAVA_LIBRARY)
+
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_MODULE:= javax.obexstatic
+
+include $(BUILD_STATIC_JAVA_LIBRARY) \ No newline at end of file
diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java
index 75278b5fd39a..cc20d391ab75 100644
--- a/obex/javax/obex/ClientOperation.java
+++ b/obex/javax/obex/ClientOperation.java
@@ -1,5 +1,6 @@
/*
- * Copyright (c) 2014 The Android Open Source Project
+ * Copyright (c) 2015 The Android Open Source Project
+ * Copyright (C) 2015 Samsung LSI
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
@@ -40,6 +41,8 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
+import android.util.Log;
+
/**
* This class implements the <code>Operation</code> interface. It will read and
* write data via puts and gets.
@@ -47,6 +50,10 @@ import java.io.ByteArrayOutputStream;
*/
public final class ClientOperation implements Operation, BaseStream {
+ private static final String TAG = "ClientOperation";
+
+ private static final boolean V = ObexHelper.VDBG;
+
private ClientSession mParent;
private boolean mInputOpen;
@@ -75,6 +82,19 @@ public final class ClientOperation implements Operation, BaseStream {
private boolean mEndOfBodySent;
+ private boolean mSendBodyHeader = true;
+ // A latch - when triggered, there is not way back ;-)
+ private boolean mSrmActive = false;
+
+ // Assume SRM disabled - until support is confirmed
+ // by the server
+ private boolean mSrmEnabled = false;
+ // keep waiting until final-bit is received in request
+ // to handle the case where the SRM enable header is in
+ // a different OBEX packet than the SRMP header.
+ private boolean mSrmWaitingForRemote = true;
+
+
/**
* Creates new OperationImpl to read and write data to a server
* @param maxSize the maximum packet size
@@ -164,7 +184,7 @@ public final class ClientOperation implements Operation, BaseStream {
* Since we are not sending any headers or returning any headers then
* we just need to write and read the same bytes
*/
- mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null);
+ mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null, false);
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) {
throw new IOException("Invalid response code from server");
@@ -215,6 +235,7 @@ public final class ClientOperation implements Operation, BaseStream {
try {
return (String)mReplyHeader.getHeader(HeaderSet.TYPE);
} catch (IOException e) {
+ if(V) Log.d(TAG, "Exception occured - returning null",e);
return null;
}
}
@@ -236,6 +257,7 @@ public final class ClientOperation implements Operation, BaseStream {
return temp.longValue();
}
} catch (IOException e) {
+ if(V) Log.d(TAG,"Exception occured - returning -1",e);
return -1;
}
}
@@ -408,7 +430,9 @@ public final class ClientOperation implements Operation, BaseStream {
}
/**
- * Sends a request to the client of the specified type
+ * Sends a request to the client of the specified type.
+ * This function will enable SRM and set SRM active if the server
+ * response allows this.
* @param opCode the request code to send to the client
* @return <code>true</code> if there is more data to send;
* <code>false</code> if there is no more data to send
@@ -431,13 +455,16 @@ public final class ClientOperation implements Operation, BaseStream {
* length, but it is a waste of resources if we can't send much of
* the body.
*/
- if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketSize) {
+ final int MINIMUM_BODY_LENGTH = 3;
+ if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length + MINIMUM_BODY_LENGTH)
+ > mMaxPacketSize) {
int end = 0;
int start = 0;
// split & send the headerArray in multiple packets.
while (end != headerArray.length) {
//split the headerArray
+
end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize
- ObexHelper.BASE_PACKET_LENGTH);
// can not split
@@ -459,7 +486,7 @@ public final class ClientOperation implements Operation, BaseStream {
byte[] sendHeader = new byte[end - start];
System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
- if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) {
+ if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput, false)) {
return false;
}
@@ -470,12 +497,20 @@ public final class ClientOperation implements Operation, BaseStream {
start = end;
}
+ // Enable SRM if it should be enabled
+ checkForSrm();
+
if (bodyLength > 0) {
return true;
} else {
return false;
}
} else {
+ /* All headers will fit into a single package */
+ if(mSendBodyHeader == false) {
+ /* As we are not to send any body data, set the FINAL_BIT */
+ opCode |= ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK;
+ }
out.write(headerArray);
}
@@ -499,11 +534,11 @@ public final class ClientOperation implements Operation, BaseStream {
* (End of Body) otherwise, we need to send 0x48 (Body)
*/
if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent)
- && ((opCode & 0x80) != 0)) {
- out.write(0x49);
+ && ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) != 0)) {
+ out.write(HeaderSet.END_OF_BODY);
mEndOfBodySent = true;
} else {
- out.write(0x48);
+ out.write(HeaderSet.BODY);
}
bodyLength += 3;
@@ -517,12 +552,11 @@ public final class ClientOperation implements Operation, BaseStream {
if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) {
// only 0x82 or 0x83 can send 0x49
- if ((opCode & 0x80) == 0) {
- out.write(0x48);
+ if ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) {
+ out.write(HeaderSet.BODY);
} else {
- out.write(0x49);
+ out.write(HeaderSet.END_OF_BODY);
mEndOfBodySent = true;
-
}
bodyLength = 3;
@@ -531,15 +565,20 @@ public final class ClientOperation implements Operation, BaseStream {
}
if (out.size() == 0) {
- if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) {
+ if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput, mSrmActive)) {
return false;
}
+ // Enable SRM if it should be enabled
+ checkForSrm();
return returnValue;
}
if ((out.size() > 0)
- && (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) {
+ && (!mParent.sendRequest(opCode, out.toByteArray(),
+ mReplyHeader, mPrivateInput, mSrmActive))) {
return false;
}
+ // Enable SRM if it should be enabled
+ checkForSrm();
// send all of the output data in 0x48,
// send 0x49 with empty body
@@ -549,6 +588,35 @@ public final class ClientOperation implements Operation, BaseStream {
return returnValue;
}
+ private void checkForSrm() throws IOException {
+ Byte srmMode = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
+ if(mParent.isSrmSupported() == true && srmMode != null
+ && srmMode == ObexHelper.OBEX_SRM_ENABLE) {
+ mSrmEnabled = true;
+ }
+ /**
+ * Call this only when a complete obex packet have been received.
+ * (This is not optimal, but the current design is not really suited to
+ * the way SRM is specified.)
+ * The BT usage of SRM is not really safe - it assumes that the SRMP will fit
+ * into every OBEX packet, hence if another header occupies the entire packet,
+ * the scheme will not work - unlikely though.
+ */
+ if(mSrmEnabled) {
+ mSrmWaitingForRemote = false;
+ Byte srmp = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
+ if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) {
+ mSrmWaitingForRemote = true;
+ // Clear the wait header, as the absence of the header in the next packet
+ // indicates don't wait anymore.
+ mReplyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
+ }
+ }
+ if((mSrmWaitingForRemote == false) && (mSrmEnabled == true)) {
+ mSrmActive = true;
+ }
+ }
+
/**
* This method starts the processing thread results. It will send the
* initial request. If the response takes more then one packet, a thread
@@ -564,40 +632,35 @@ public final class ClientOperation implements Operation, BaseStream {
if (mGetOperation) {
if (!mOperationDone) {
- if (!mGetFinalFlag) {
- mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
- while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
- more = sendRequest(0x03);
- }
-
- if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
- mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
- }
- if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
- mOperationDone = true;
- }
- } else {
- more = sendRequest(0x83);
-
- if (more) {
- throw new IOException("FINAL_GET forced but data did not fit into single packet!");
- }
-
+ mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
+ while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
+ more = sendRequest(ObexHelper.OBEX_OPCODE_GET);
+ }
+ // For GET we need to loop until all headers have been sent,
+ // And then we wait for the first continue package with the
+ // reply.
+ if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
+ mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL,
+ null, mReplyHeader, mPrivateInput, mSrmActive);
+ }
+ if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true;
+ } else {
+ checkForSrm();
}
}
} else {
-
+ // PUT operation
if (!mOperationDone) {
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
- more = sendRequest(0x02);
-
+ more = sendRequest(ObexHelper.OBEX_OPCODE_PUT);
}
}
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
- mParent.sendRequest(0x82, null, mReplyHeader, mPrivateInput);
+ mParent.sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL,
+ null, mReplyHeader, mPrivateInput, mSrmActive);
}
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
@@ -617,15 +680,21 @@ public final class ClientOperation implements Operation, BaseStream {
public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
throws IOException {
+ // One path to the first put operation - the other one does not need to
+ // handle SRM, as all will fit into one packet.
+
if (mGetOperation) {
if ((inStream) && (!mOperationDone)) {
// to deal with inputstream in get operation
- mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
+ mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL,
+ null, mReplyHeader, mPrivateInput, mSrmActive);
/*
* Determine if that was not the last packet in the operation
*/
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true;
+ } else {
+ checkForSrm();
}
return true;
@@ -636,16 +705,7 @@ public final class ClientOperation implements Operation, BaseStream {
if (mPrivateInput == null) {
mPrivateInput = new PrivateInputStream(this);
}
-
- if (!mGetFinalFlag) {
- sendRequest(0x03);
- } else {
- sendRequest(0x83);
-
- if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
- mOperationDone = true;
- }
- }
+ sendRequest(ObexHelper.OBEX_OPCODE_GET);
return true;
} else if (mOperationDone) {
@@ -653,12 +713,13 @@ public final class ClientOperation implements Operation, BaseStream {
}
} else {
+ // PUT operation
if ((!inStream) && (!mOperationDone)) {
// to deal with outputstream in put operation
if (mReplyHeader.responseCode == -1) {
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
}
- sendRequest(0x02);
+ sendRequest(ObexHelper.OBEX_OPCODE_PUT);
return true;
} else if ((inStream) && (!mOperationDone)) {
// How to deal with inputstream in put operation ?
@@ -696,7 +757,7 @@ public final class ClientOperation implements Operation, BaseStream {
}
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
- more = sendRequest(0x02);
+ more = sendRequest(ObexHelper.OBEX_OPCODE_PUT);
}
/*
@@ -706,7 +767,7 @@ public final class ClientOperation implements Operation, BaseStream {
*/
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
- sendRequest(0x82);
+ sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL);
}
mOperationDone = true;
} else if ((inStream) && (mOperationDone)) {
@@ -724,12 +785,14 @@ public final class ClientOperation implements Operation, BaseStream {
}
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
- if (!sendRequest(0x83)) {
+ if (!sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL)) {
break;
}
}
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
- mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
+ mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, null,
+ mReplyHeader, mPrivateInput, false);
+ // Regardless of the SRM state, wait for the response.
}
mOperationDone = true;
} else if ((!inStream) && (!mOperationDone)) {
@@ -752,9 +815,9 @@ public final class ClientOperation implements Operation, BaseStream {
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
- more = sendRequest(0x03);
+ more = sendRequest(ObexHelper.OBEX_OPCODE_GET);
}
- sendRequest(0x83);
+ sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL);
// parent.sendRequest(0x83, null, replyHeaders, privateInput);
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true;
@@ -764,5 +827,6 @@ public final class ClientOperation implements Operation, BaseStream {
}
public void noBodyHeader(){
+ mSendBodyHeader = false;
}
}
diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java
index 27d8976d0702..272a920754f5 100644
--- a/obex/javax/obex/ClientSession.java
+++ b/obex/javax/obex/ClientSession.java
@@ -1,4 +1,6 @@
/*
+ * Copyright (c) 2015 The Android Open Source Project
+ * Copyright (C) 2015 Samsung LSI
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
@@ -37,12 +39,16 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import android.util.Log;
+
/**
* This class in an implementation of the OBEX ClientSession.
* @hide
*/
public final class ClientSession extends ObexSession {
+ private static final String TAG = "ClientSession";
+
private boolean mOpen;
// Determines if an OBEX layer connection has been established
@@ -51,10 +57,10 @@ public final class ClientSession extends ObexSession {
private byte[] mConnectionId = null;
/*
- * The max Packet size must be at least 256 according to the OBEX
+ * The max Packet size must be at least 255 according to the OBEX
* specification.
*/
- private int maxPacketSize = 256;
+ private int mMaxTxPacketSize = ObexHelper.LOWER_LIMIT_MAX_PACKET_SIZE;
private boolean mRequestActive;
@@ -62,11 +68,33 @@ public final class ClientSession extends ObexSession {
private final OutputStream mOutput;
+ private final boolean mLocalSrmSupported;
+
+ private final ObexTransport mTransport;
+
public ClientSession(final ObexTransport trans) throws IOException {
mInput = trans.openInputStream();
mOutput = trans.openOutputStream();
mOpen = true;
mRequestActive = false;
+ mLocalSrmSupported = trans.isSrmSupported();
+ mTransport = trans;
+ }
+
+ /**
+ * Create a ClientSession
+ * @param trans The transport to use for OBEX transactions
+ * @param supportsSrm True if Single Response Mode should be used e.g. if the
+ * supplied transport is a TCP or l2cap channel.
+ * @throws IOException if it occurs while opening the transport streams.
+ */
+ public ClientSession(final ObexTransport trans, final boolean supportsSrm) throws IOException {
+ mInput = trans.openInputStream();
+ mOutput = trans.openOutputStream();
+ mOpen = true;
+ mRequestActive = false;
+ mLocalSrmSupported = supportsSrm;
+ mTransport = trans;
}
public HeaderSet connect(final HeaderSet header) throws IOException {
@@ -98,23 +126,25 @@ public final class ClientSession extends ObexSession {
* Byte 7 to n: headers
*/
byte[] requestPacket = new byte[totalLength];
+ int maxRxPacketSize = ObexHelper.getMaxRxPacketSize(mTransport);
// We just need to start at byte 3 since the sendRequest() method will
// handle the length and 0x80.
requestPacket[0] = (byte)0x10;
requestPacket[1] = (byte)0x00;
- requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8);
- requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF);
+ requestPacket[2] = (byte)(maxRxPacketSize >> 8);
+ requestPacket[3] = (byte)(maxRxPacketSize & 0xFF);
if (head != null) {
System.arraycopy(head, 0, requestPacket, 4, head.length);
}
- // check with local max packet size
+ // Since we are not yet connected, the peer max packet size is unknown,
+ // hence we are only guaranteed the server will use the first 7 bytes.
if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
- throw new IOException("Packet size exceeds max packet size");
+ throw new IOException("Packet size exceeds max packet size for connect");
}
HeaderSet returnHeaderSet = new HeaderSet();
- sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null);
+ sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null, false);
/*
* Read the response from the OBEX server.
@@ -158,7 +188,18 @@ public final class ClientSession extends ObexSession {
System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
}
- return new ClientOperation(maxPacketSize, this, head, true);
+ if(mLocalSrmSupported) {
+ head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE);
+ /* TODO: Consider creating an interface to get the wait state.
+ * On an android system, I cannot see when this is to be used.
+ * except perhaps if we are to wait for user accept on a push message.
+ if(getLocalWaitState()) {
+ head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT);
+ }
+ */
+ }
+
+ return new ClientOperation(mMaxTxPacketSize, this, head, true);
}
/**
@@ -202,7 +243,7 @@ public final class ClientSession extends ObexSession {
}
head = ObexHelper.createHeader(header, false);
- if ((head.length + 3) > maxPacketSize) {
+ if ((head.length + 3) > mMaxTxPacketSize) {
throw new IOException("Packet size exceeds max packet size");
}
} else {
@@ -215,7 +256,7 @@ public final class ClientSession extends ObexSession {
}
HeaderSet returnHeaderSet = new HeaderSet();
- sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null);
+ sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null, false);
/*
* An OBEX DISCONNECT reply from the server:
@@ -269,7 +310,16 @@ public final class ClientSession extends ObexSession {
System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
}
- return new ClientOperation(maxPacketSize, this, head, false);
+ if(mLocalSrmSupported) {
+ head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE);
+ /* TODO: Consider creating an interface to get the wait state.
+ * On an android system, I cannot see when this is to be used.
+ if(getLocalWaitState()) {
+ head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT);
+ }
+ */
+ }
+ return new ClientOperation(mMaxTxPacketSize, this, head, false);
}
public void setAuthenticator(Authenticator auth) throws IOException {
@@ -314,7 +364,7 @@ public final class ClientSession extends ObexSession {
head = ObexHelper.createHeader(headset, false);
totalLength += head.length;
- if (totalLength > maxPacketSize) {
+ if (totalLength > mMaxTxPacketSize) {
throw new IOException("Packet size exceeds max packet size");
}
@@ -348,7 +398,7 @@ public final class ClientSession extends ObexSession {
}
HeaderSet returnHeaderSet = new HeaderSet();
- sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null);
+ sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null, false);
/*
* An OBEX SETPATH reply from the server:
@@ -400,20 +450,40 @@ public final class ClientSession extends ObexSession {
* @param head the headers to send to the client
* @param header the header object to update with the response
* @param privateInput the input stream used by the Operation object; null
- * if this is called on a CONNECT, SETPATH or DISCONNECT return
+ * if this is called on a CONNECT, SETPATH or DISCONNECT
+ * @return
* <code>true</code> if the operation completed successfully;
* <code>false</code> if an authentication response failed to pass
* @throws IOException if an IO error occurs
*/
public boolean sendRequest(int opCode, byte[] head, HeaderSet header,
- PrivateInputStream privateInput) throws IOException {
+ PrivateInputStream privateInput, boolean srmActive) throws IOException {
//check header length with local max size
if (head != null) {
if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
+ // TODO: This is an implementation limit - not a specification requirement.
throw new IOException("header too large ");
}
}
+ boolean skipSend = false;
+ boolean skipReceive = false;
+ if (srmActive == true) {
+ if (opCode == ObexHelper.OBEX_OPCODE_PUT) {
+ // we are in the middle of a SRM PUT operation, don't expect a continue.
+ skipReceive = true;
+ } else if (opCode == ObexHelper.OBEX_OPCODE_GET) {
+ // We are still sending the get request, send, but don't expect continue
+ // until the request is transfered (the final bit is set)
+ skipReceive = true;
+ } else if (opCode == ObexHelper.OBEX_OPCODE_GET_FINAL) {
+ // All done sending the request, expect data from the server, without
+ // sending continue.
+ skipSend = true;
+ }
+
+ }
+
int bytesReceived;
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write((byte)opCode);
@@ -428,86 +498,105 @@ public final class ClientSession extends ObexSession {
out.write(head);
}
- // Write the request to the output stream and flush the stream
- mOutput.write(out.toByteArray());
- mOutput.flush();
+ if (!skipSend) {
+ // Write the request to the output stream and flush the stream
+ mOutput.write(out.toByteArray());
+ // TODO: is this really needed? if this flush is implemented
+ // correctly, we will get a gap between each obex packet.
+ // which is kind of the idea behind SRM to avoid.
+ // Consider offloading to another thread (async action)
+ mOutput.flush();
+ }
- header.responseCode = mInput.read();
+ if (!skipReceive) {
+ header.responseCode = mInput.read();
- int length = ((mInput.read() << 8) | (mInput.read()));
+ int length = ((mInput.read() << 8) | (mInput.read()));
- if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
- throw new IOException("Packet received exceeds packet size limit");
- }
- if (length > ObexHelper.BASE_PACKET_LENGTH) {
- byte[] data = null;
- if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) {
- @SuppressWarnings("unused")
- int version = mInput.read();
- @SuppressWarnings("unused")
- int flags = mInput.read();
- maxPacketSize = (mInput.read() << 8) + mInput.read();
+ if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
+ throw new IOException("Packet received exceeds packet size limit");
+ }
+ if (length > ObexHelper.BASE_PACKET_LENGTH) {
+ byte[] data = null;
+ if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) {
+ @SuppressWarnings("unused")
+ int version = mInput.read();
+ @SuppressWarnings("unused")
+ int flags = mInput.read();
+ mMaxTxPacketSize = (mInput.read() << 8) + mInput.read();
+
+ //check with local max size
+ if (mMaxTxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
+ mMaxTxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
+ }
- //check with local max size
- if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
- maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
- }
+ // check with transport maximum size
+ if(mMaxTxPacketSize > ObexHelper.getMaxTxPacketSize(mTransport)) {
+ // To increase this size, increase the buffer size in L2CAP layer
+ // in Bluedroid.
+ Log.w(TAG, "An OBEX packet size of " + mMaxTxPacketSize + "was"
+ + " requested. Transport only allows: "
+ + ObexHelper.getMaxTxPacketSize(mTransport)
+ + " Lowering limit to this value.");
+ mMaxTxPacketSize = ObexHelper.getMaxTxPacketSize(mTransport);
+ }
- if (length > 7) {
- data = new byte[length - 7];
+ if (length > 7) {
+ data = new byte[length - 7];
- bytesReceived = mInput.read(data);
- while (bytesReceived != (length - 7)) {
- bytesReceived += mInput.read(data, bytesReceived, data.length
- - bytesReceived);
+ bytesReceived = mInput.read(data);
+ while (bytesReceived != (length - 7)) {
+ bytesReceived += mInput.read(data, bytesReceived, data.length
+ - bytesReceived);
+ }
+ } else {
+ return true;
}
} else {
- return true;
- }
- } else {
- data = new byte[length - 3];
- bytesReceived = mInput.read(data);
+ data = new byte[length - 3];
+ bytesReceived = mInput.read(data);
- while (bytesReceived != (length - 3)) {
- bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived);
- }
- if (opCode == ObexHelper.OBEX_OPCODE_ABORT) {
- return true;
+ while (bytesReceived != (length - 3)) {
+ bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived);
+ }
+ if (opCode == ObexHelper.OBEX_OPCODE_ABORT) {
+ return true;
+ }
}
- }
- byte[] body = ObexHelper.updateHeaderSet(header, data);
- if ((privateInput != null) && (body != null)) {
- privateInput.writeBytes(body, 1);
- }
+ byte[] body = ObexHelper.updateHeaderSet(header, data);
+ if ((privateInput != null) && (body != null)) {
+ privateInput.writeBytes(body, 1);
+ }
- if (header.mConnectionID != null) {
- mConnectionId = new byte[4];
- System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4);
- }
+ if (header.mConnectionID != null) {
+ mConnectionId = new byte[4];
+ System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4);
+ }
- if (header.mAuthResp != null) {
- if (!handleAuthResp(header.mAuthResp)) {
- setRequestInactive();
- throw new IOException("Authentication Failed");
+ if (header.mAuthResp != null) {
+ if (!handleAuthResp(header.mAuthResp)) {
+ setRequestInactive();
+ throw new IOException("Authentication Failed");
+ }
}
- }
- if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED)
- && (header.mAuthChall != null)) {
+ if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED)
+ && (header.mAuthChall != null)) {
- if (handleAuthChall(header)) {
- out.write((byte)HeaderSet.AUTH_RESPONSE);
- out.write((byte)((header.mAuthResp.length + 3) >> 8));
- out.write((byte)(header.mAuthResp.length + 3));
- out.write(header.mAuthResp);
- header.mAuthChall = null;
- header.mAuthResp = null;
+ if (handleAuthChall(header)) {
+ out.write((byte)HeaderSet.AUTH_RESPONSE);
+ out.write((byte)((header.mAuthResp.length + 3) >> 8));
+ out.write((byte)(header.mAuthResp.length + 3));
+ out.write(header.mAuthResp);
+ header.mAuthChall = null;
+ header.mAuthResp = null;
- byte[] sendHeaders = new byte[out.size() - 3];
- System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length);
+ byte[] sendHeaders = new byte[out.size() - 3];
+ System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length);
- return sendRequest(opCode, sendHeaders, header, privateInput);
+ return sendRequest(opCode, sendHeaders, header, privateInput, false);
+ }
}
}
}
@@ -520,4 +609,8 @@ public final class ClientSession extends ObexSession {
mInput.close();
mOutput.close();
}
+
+ public boolean isSrmSupported() {
+ return mLocalSrmSupported;
+ }
}
diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java
index 51b560a5060b..35fe1863e1fb 100644
--- a/obex/javax/obex/HeaderSet.java
+++ b/obex/javax/obex/HeaderSet.java
@@ -40,7 +40,7 @@ import java.security.SecureRandom;
/**
* This class implements the javax.obex.HeaderSet interface for OBEX over
- * RFCOMM.
+ * RFCOMM or OBEX over l2cap.
* @hide
*/
public final class HeaderSet {
@@ -178,6 +178,22 @@ public final class HeaderSet {
*/
public static final int OBJECT_CLASS = 0x4F;
+ /**
+ * Represents the OBEX Single Response Mode (SRM). This header is used
+ * for Single response mode, introduced in OBEX 1.5.
+ * <P>
+ * The value of <code>SINGLE_RESPONSE_MODE</code> is 0x97 (151).
+ */
+ public static final int SINGLE_RESPONSE_MODE = 0x97;
+
+ /**
+ * Represents the OBEX Single Response Mode Parameters. This header is used
+ * for Single response mode, introduced in OBEX 1.5.
+ * <P>
+ * The value of <code>SINGLE_RESPONSE_MODE_PARAMETER</code> is 0x98 (152).
+ */
+ public static final int SINGLE_RESPONSE_MODE_PARAMETER = 0x98;
+
private Long mCount; // 4 byte unsigned integer
private String mName; // null terminated Unicode text string
@@ -204,7 +220,7 @@ public final class HeaderSet {
private byte[] mObjectClass; // byte sequence
- private String[] mUnicodeUserDefined; //null terminated unicode string
+ private String[] mUnicodeUserDefined; // null terminated unicode string
private byte[][] mSequenceUserDefined; // byte sequence user defined
@@ -212,7 +228,12 @@ public final class HeaderSet {
private Long[] mIntegerUserDefined; // 4 byte unsigned integer
- private final SecureRandom mRandom;
+ private SecureRandom mRandom = null;
+
+ private Byte mSingleResponseMode; // byte to indicate enable/disable/support for SRM
+
+ private Byte mSrmParam; // byte representing the SRM parameters - only "wait"
+ // is supported by Bluetooth
/*package*/ byte[] nonce;
@@ -234,7 +255,6 @@ public final class HeaderSet {
mByteUserDefined = new Byte[16];
mIntegerUserDefined = new Long[16];
responseCode = -1;
- mRandom = new SecureRandom();
}
/**
@@ -393,6 +413,30 @@ public final class HeaderSet {
}
}
break;
+ case SINGLE_RESPONSE_MODE:
+ if (headerValue == null) {
+ mSingleResponseMode = null;
+ } else {
+ if (!(headerValue instanceof Byte)) {
+ throw new IllegalArgumentException(
+ "Single Response Mode must be a Byte");
+ } else {
+ mSingleResponseMode = (Byte)headerValue;
+ }
+ }
+ break;
+ case SINGLE_RESPONSE_MODE_PARAMETER:
+ if (headerValue == null) {
+ mSrmParam = null;
+ } else {
+ if (!(headerValue instanceof Byte)) {
+ throw new IllegalArgumentException(
+ "Single Response Mode Parameter must be a Byte");
+ } else {
+ mSrmParam = (Byte)headerValue;
+ }
+ }
+ break;
default:
// Verify that it was not a Unicode String user Defined
if ((headerID >= 0x30) && (headerID <= 0x3F)) {
@@ -493,6 +537,10 @@ public final class HeaderSet {
return mObjectClass;
case APPLICATION_PARAMETER:
return mAppParam;
+ case SINGLE_RESPONSE_MODE:
+ return mSingleResponseMode;
+ case SINGLE_RESPONSE_MODE_PARAMETER:
+ return mSrmParam;
default:
// Verify that it was not a Unicode String user Defined
if ((headerID >= 0x30) && (headerID <= 0x3F)) {
@@ -564,6 +612,12 @@ public final class HeaderSet {
if (mObjectClass != null) {
out.write(OBJECT_CLASS);
}
+ if(mSingleResponseMode != null) {
+ out.write(SINGLE_RESPONSE_MODE);
+ }
+ if(mSrmParam != null) {
+ out.write(SINGLE_RESPONSE_MODE_PARAMETER);
+ }
for (int i = 0x30; i < 0x40; i++) {
if (mUnicodeUserDefined[i - 0x30] != null) {
@@ -625,6 +679,9 @@ public final class HeaderSet {
throws IOException {
nonce = new byte[16];
+ if(mRandom == null) {
+ mRandom = new SecureRandom();
+ }
for (int i = 0; i < 16; i++) {
nonce[i] = (byte)mRandom.nextInt();
}
diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java
index 0a06709548fe..fa50943343e9 100644
--- a/obex/javax/obex/ObexHelper.java
+++ b/obex/javax/obex/ObexHelper.java
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2015 Samsung LSI
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
@@ -42,12 +43,16 @@ import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
+import android.util.Log;
+
/**
* This class defines a set of helper methods for the implementation of Obex.
* @hide
*/
public final class ObexHelper {
+ private static final String TAG = "ObexHelper";
+ public static final boolean VDBG = false;
/**
* Defines the basic packet length used by OBEX. Every OBEX packet has the
* same basic format:<BR>
@@ -65,18 +70,24 @@ public final class ObexHelper {
* should be the Max incoming MTU minus TODO: L2CAP package headers and
* RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO:
* LocalDevice.getProperty().
+ * NOTE: This value must be larger than or equal to the L2CAP SDU
*/
/*
* android note set as 0xFFFE to match remote MPS
*/
public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
+ // The minimum allowed max packet size is 255 according to the OBEX specification
+ public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
+
/**
* Temporary workaround to be able to push files to Windows 7.
* TODO: Should be removed as soon as Microsoft updates their driver.
*/
public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
+ public static final int OBEX_OPCODE_FINAL_BIT_MASK = 0x80;
+
public static final int OBEX_OPCODE_CONNECT = 0x80;
public static final int OBEX_OPCODE_DISCONNECT = 0x81;
@@ -119,6 +130,12 @@ public final class ObexHelper {
public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF;
+ public static final byte OBEX_SRM_ENABLE = 0x01; // For BT we only need enable/disable
+ public static final byte OBEX_SRM_DISABLE = 0x00;
+ public static final byte OBEX_SRM_SUPPORT = 0x02; // Unused for now
+
+ public static final byte OBEX_SRMP_WAIT = 0x01; // Only SRMP value used by BT
+
/**
* Updates the HeaderSet with the headers received in the byte array
* provided. Invalid headers are ignored.
@@ -314,7 +331,7 @@ public final class ObexHelper {
}
} catch (Exception e) {
// Not a valid header so ignore
- throw new IOException("Header was not formatted properly");
+ throw new IOException("Header was not formatted properly", e);
}
index += 4;
break;
@@ -322,7 +339,7 @@ public final class ObexHelper {
}
} catch (IOException e) {
- throw new IOException("Header was not formatted properly");
+ throw new IOException("Header was not formatted properly", e);
}
return body;
@@ -672,6 +689,33 @@ public final class ObexHelper {
}
}
+ // TODO:
+ // If the SRM and SRMP header is in use, they must be send in the same OBEX packet
+ // But the current structure of the obex code cannot handle this, and therefore
+ // it makes sense to put them in the tail of the headers, since we then reduce the
+ // chance of enabling SRM to soon. The down side is that SRM cannot be used while
+ // transferring non-body headers
+
+ // Add the SRM header
+ byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
+ if (byteHeader != null) {
+ out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE);
+ out.write(byteHeader.byteValue());
+ if (nullOut) {
+ headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, null);
+ }
+ }
+
+ // Add the SRM parameter header
+ byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
+ if (byteHeader != null) {
+ out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
+ out.write(byteHeader.byteValue());
+ if (nullOut) {
+ headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
+ }
+ }
+
} catch (IOException e) {
} finally {
result = out.toByteArray();
@@ -702,6 +746,8 @@ public final class ObexHelper {
int index = start;
int length = 0;
+ // TODO: Ensure SRM and SRMP headers are not split into two OBEX packets
+
while ((fullLength < maxSize) && (index < headerArray.length)) {
int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]);
lastLength = fullLength;
@@ -1008,4 +1054,39 @@ public final class ObexHelper {
return authChall;
}
+
+ /**
+ * Return the maximum allowed OBEX packet to transmit.
+ * OBEX packets transmitted must be smaller than this value.
+ * @param transport Reference to the ObexTransport in use.
+ * @return the maximum allowed OBEX packet to transmit
+ */
+ public static int getMaxTxPacketSize(ObexTransport transport) {
+ int size = transport.getMaxTransmitPacketSize();
+ return validateMaxPacketSize(size);
+ }
+
+ /**
+ * Return the maximum allowed OBEX packet to receive - used in OBEX connect.
+ * @param transport
+ * @return he maximum allowed OBEX packet to receive
+ */
+ public static int getMaxRxPacketSize(ObexTransport transport) {
+ int size = transport.getMaxReceivePacketSize();
+ return validateMaxPacketSize(size);
+ }
+
+ private static int validateMaxPacketSize(int size) {
+ if(VDBG && (size > MAX_PACKET_SIZE_INT)) Log.w(TAG,
+ "The packet size supported for the connection (" + size + ") is larger"
+ + " than the configured OBEX packet size: " + MAX_PACKET_SIZE_INT);
+ if(size != -1) {
+ if(size < LOWER_LIMIT_MAX_PACKET_SIZE) {
+ throw new IllegalArgumentException(size + " is less that the lower limit: "
+ + LOWER_LIMIT_MAX_PACKET_SIZE);
+ }
+ return size;
+ }
+ return MAX_PACKET_SIZE_INT;
+ }
}
diff --git a/obex/javax/obex/ObexPacket.java b/obex/javax/obex/ObexPacket.java
new file mode 100644
index 000000000000..bb6c96e5b4a5
--- /dev/null
+++ b/obex/javax/obex/ObexPacket.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2015 The Android Open Source Project
+ * Copyright (c) 2015 Samsung LSI
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package javax.obex;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ObexPacket {
+ public int mHeaderId;
+ public int mLength;
+ public byte[] mPayload = null;
+
+ private ObexPacket(int headerId, int length) {
+ mHeaderId = headerId;
+ mLength = length;
+ }
+
+ /**
+ * Create a complete OBEX packet by reading data from an InputStream.
+ * @param is the input stream to read from.
+ * @return the OBEX packet read.
+ * @throws IOException if an IO exception occurs during read.
+ */
+ public static ObexPacket read(InputStream is) throws IOException {
+ int headerId = is.read();
+ return read(headerId, is);
+ }
+
+ /**
+ * Read the remainder of an OBEX packet, with a specified headerId.
+ * @param headerId the headerId already read from the stream.
+ * @param is the stream to read from, assuming 1 byte have already been read.
+ * @return the OBEX packet read.
+ * @throws IOException
+ */
+ public static ObexPacket read(int headerId, InputStream is) throws IOException {
+ // Read the 2 byte length field from the stream
+ int length = is.read();
+ length = (length << 8) + is.read();
+
+ ObexPacket newPacket = new ObexPacket(headerId, length);
+
+ int bytesReceived;
+ byte[] temp = null;
+ if (length > 3) {
+ // First three bytes already read, compensating for this
+ temp = new byte[length - 3];
+ bytesReceived = is.read(temp);
+ while (bytesReceived != temp.length) {
+ bytesReceived += is.read(temp, bytesReceived, temp.length - bytesReceived);
+ }
+ }
+ newPacket.mPayload = temp;
+ return newPacket;
+ }
+}
diff --git a/obex/javax/obex/ObexSession.java b/obex/javax/obex/ObexSession.java
index a7daeb533e16..542b9c8b2492 100644
--- a/obex/javax/obex/ObexSession.java
+++ b/obex/javax/obex/ObexSession.java
@@ -34,6 +34,8 @@ package javax.obex;
import java.io.IOException;
+import android.util.Log;
+
/**
* The <code>ObexSession</code> interface characterizes the term
* "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which
@@ -47,6 +49,9 @@ import java.io.IOException;
*/
public class ObexSession {
+ private static final String TAG = "ObexSession";
+ private static final boolean V = ObexHelper.VDBG;
+
protected Authenticator mAuthenticator;
protected byte[] mChallengeDigest;
@@ -125,6 +130,7 @@ public class ObexSession {
result = mAuthenticator
.onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess);
} catch (Exception e) {
+ if (V) Log.d(TAG, "Exception occured - returning false", e);
return false;
}
diff --git a/obex/javax/obex/ObexTransport.java b/obex/javax/obex/ObexTransport.java
index 445e2675b212..a5a75f55f553 100644
--- a/obex/javax/obex/ObexTransport.java
+++ b/obex/javax/obex/ObexTransport.java
@@ -73,4 +73,39 @@ public interface ObexTransport {
DataOutputStream openDataOutputStream() throws IOException;
+ /**
+ * Must return the maximum allowed OBEX packet that can be sent over
+ * the transport. For L2CAP this will be the Max SDU reported by the
+ * peer device.
+ * The returned value will be used to set the outgoing OBEX packet
+ * size. Therefore this value shall not change.
+ * For RFCOMM or other transport types where the OBEX packets size
+ * is unrelated to the transport packet size, return -1;
+ * @return the maximum allowed OBEX packet that can be send over
+ * the transport. Or -1 in case of don't care.
+ */
+ int getMaxTransmitPacketSize();
+
+ /**
+ * Must return the maximum allowed OBEX packet that can be received over
+ * the transport. For L2CAP this will be the Max SDU configured for the
+ * L2CAP channel.
+ * The returned value will be used to validate the incoming packet size
+ * values.
+ * For RFCOMM or other transport types where the OBEX packets size
+ * is unrelated to the transport packet size, return -1;
+ * @return the maximum allowed OBEX packet that can be send over
+ * the transport. Or -1 in case of don't care.
+ */
+ int getMaxReceivePacketSize();
+
+ /**
+ * Shall return true if the transport in use supports SRM.
+ * @return
+ * <code>true</code> if SRM operation is supported, and is to be enabled.
+ * <code>false</code> if SRM operations are not supported, or should not be used.
+ */
+ boolean isSrmSupported();
+
+
}
diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java
index fc441e0e165c..56a675acf082 100644
--- a/obex/javax/obex/ServerOperation.java
+++ b/obex/javax/obex/ServerOperation.java
@@ -1,4 +1,5 @@
-/*
+/* Copyright (c) 2015 The Android Open Source Project
+ * Copyright (C) 2015 Samsung LSI
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
@@ -39,6 +40,8 @@ import java.io.OutputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
+import android.util.Log;
+
/**
* This class implements the Operation interface for server side connections.
* <P>
@@ -54,6 +57,10 @@ import java.io.ByteArrayOutputStream;
*/
public final class ServerOperation implements Operation, BaseStream {
+ private static final String TAG = "ServerOperation";
+
+ private static final boolean V = ObexHelper.VDBG; // Verbose debugging
+
public boolean isAborted;
public HeaderSet requestHeader;
@@ -78,6 +85,8 @@ public final class ServerOperation implements Operation, BaseStream {
private PrivateOutputStream mPrivateOutput;
+ private ObexTransport mTransport;
+
private boolean mPrivateOutputOpen;
private String mExceptionString;
@@ -89,6 +98,19 @@ public final class ServerOperation implements Operation, BaseStream {
private boolean mHasBody;
private boolean mSendBodyHeader = true;
+ // Assume SRM disabled - needs to be explicit
+ // enabled by client
+ private boolean mSrmEnabled = false;
+ // A latch - when triggered, there is not way back ;-)
+ private boolean mSrmActive = false;
+ // Set to true when a SRM enable response have been send
+ private boolean mSrmResponseSent = false;
+ // keep waiting until final-bit is received in request
+ // to handle the case where the SRM enable header is in
+ // a different OBEX packet than the SRMP header.
+ private boolean mSrmWaitingForRemote = true;
+ // Why should we wait? - currently not exposed to apps.
+ private boolean mSrmLocalWait = false;
/**
* Creates new ServerOperation
@@ -116,12 +138,14 @@ public final class ServerOperation implements Operation, BaseStream {
mRequestFinished = false;
mPrivateOutputOpen = false;
mHasBody = false;
- int bytesReceived;
+ ObexPacket packet;
+ mTransport = p.getTransport();
/*
* Determine if this is a PUT request
*/
- if ((request == 0x02) || (request == 0x82)) {
+ if ((request == ObexHelper.OBEX_OPCODE_PUT) ||
+ (request == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
/*
* It is a PUT request.
*/
@@ -130,13 +154,14 @@ public final class ServerOperation implements Operation, BaseStream {
/*
* Determine if the final bit is set
*/
- if ((request & 0x80) == 0) {
+ if ((request & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) {
finalBitSet = false;
} else {
finalBitSet = true;
mRequestFinished = true;
}
- } else if ((request == 0x03) || (request == 0x83)) {
+ } else if ((request == ObexHelper.OBEX_OPCODE_GET) ||
+ (request == ObexHelper.OBEX_OPCODE_GET_FINAL)) {
/*
* It is a GET request.
*/
@@ -145,71 +170,32 @@ public final class ServerOperation implements Operation, BaseStream {
// For Get request, final bit set is decided by server side logic
finalBitSet = false;
- if (request == 0x83) {
+ if (request == ObexHelper.OBEX_OPCODE_GET_FINAL) {
mRequestFinished = true;
}
} else {
throw new IOException("ServerOperation can not handle such request");
}
- int length = in.read();
- length = (length << 8) + in.read();
+ packet = ObexPacket.read(request, mInput);
/*
* Determine if the packet length is larger than this device can receive
*/
- if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
+ if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
- throw new IOException("Packet received was too large");
+ throw new IOException("Packet received was too large. Length: "
+ + packet.mLength + " maxLength: " + ObexHelper.getMaxRxPacketSize(mTransport));
}
/*
* Determine if any headers were sent in the initial request
*/
- if (length > 3) {
- byte[] data = new byte[length - 3];
- bytesReceived = in.read(data);
-
- while (bytesReceived != data.length) {
- bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived);
+ if (packet.mLength > 3) {
+ if(!handleObexPacket(packet)) {
+ return;
}
-
- byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
-
- if (body != null) {
- mHasBody = true;
- }
-
- if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
- mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID));
- } else {
- mListener.setConnectionId(1);
- }
-
- if (requestHeader.mAuthResp != null) {
- if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
- mExceptionString = "Authentication Failed";
- mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
- mClosed = true;
- requestHeader.mAuthResp = null;
- return;
- }
- }
-
- if (requestHeader.mAuthChall != null) {
- mParent.handleAuthChall(requestHeader);
- // send the authResp to the client
- replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
- System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
- replyHeader.mAuthResp.length);
- requestHeader.mAuthResp = null;
- requestHeader.mAuthChall = null;
-
- }
-
- if (body != null) {
- mPrivateInput.writeBytes(body, 1);
- } else {
+ if (!mHasBody) {
while ((!mGetOperation) && (!finalBitSet)) {
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
if (mPrivateInput.available() > 0) {
@@ -232,6 +218,100 @@ public final class ServerOperation implements Operation, BaseStream {
}
}
+ /**
+ * Parse headers and update member variables
+ * @param packet the received obex packet
+ * @return false for failing authentication - and a OBEX_HTTP_UNAUTHORIZED
+ * response have been send. Else true.
+ * @throws IOException
+ */
+ private boolean handleObexPacket(ObexPacket packet) throws IOException {
+ byte[] body = updateRequestHeaders(packet);
+
+ if (body != null) {
+ mHasBody = true;
+ }
+ if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
+ mListener.setConnectionId(ObexHelper
+ .convertToLong(requestHeader.mConnectionID));
+ } else {
+ mListener.setConnectionId(1);
+ }
+
+ if (requestHeader.mAuthResp != null) {
+ if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
+ mExceptionString = "Authentication Failed";
+ mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
+ mClosed = true;
+ requestHeader.mAuthResp = null;
+ return false;
+ }
+ requestHeader.mAuthResp = null;
+ }
+
+ if (requestHeader.mAuthChall != null) {
+ mParent.handleAuthChall(requestHeader);
+ // send the auhtResp to the client
+ replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
+ System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
+ replyHeader.mAuthResp.length);
+ requestHeader.mAuthResp = null;
+ requestHeader.mAuthChall = null;
+ }
+
+ if (body != null) {
+ mPrivateInput.writeBytes(body, 1);
+ }
+ return true;
+ }
+
+ /**
+ * Update the request header set, and sniff on SRM headers to update local state.
+ * @param data the OBEX packet data
+ * @return any bytes in a body/end-of-body header returned by {@link ObexHelper.updateHeaderSet}
+ * @throws IOException
+ */
+ private byte[] updateRequestHeaders(ObexPacket packet) throws IOException {
+ byte[] body = null;
+ if (packet.mPayload != null) {
+ body = ObexHelper.updateHeaderSet(requestHeader, packet.mPayload);
+ }
+ Byte srmMode = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
+ if(mTransport.isSrmSupported() && srmMode != null
+ && srmMode == ObexHelper.OBEX_SRM_ENABLE) {
+ mSrmEnabled = true;
+ if(V) Log.d(TAG,"SRM is now ENABLED (but not active) for this operation");
+ }
+ checkForSrmWait(packet.mHeaderId);
+ if((!mSrmWaitingForRemote) && (mSrmEnabled)) {
+ if(V) Log.d(TAG,"SRM is now ACTIVE for this operation");
+ mSrmActive = true;
+ }
+ return body;
+ }
+
+ /**
+ * Call this only when a complete request have been received.
+ * (This is not optimal, but the current design is not really suited to
+ * the way SRM is specified.)
+ */
+ private void checkForSrmWait(int headerId){
+ if (mSrmEnabled && (headerId == ObexHelper.OBEX_OPCODE_GET
+ || headerId == ObexHelper.OBEX_OPCODE_GET_FINAL
+ || headerId == ObexHelper.OBEX_OPCODE_PUT)) {
+ try {
+ mSrmWaitingForRemote = false;
+ Byte srmp = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
+ if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) {
+ mSrmWaitingForRemote = true;
+ // Clear the wait header, as the absents of the header when the final bit is set
+ // indicates don't wait.
+ requestHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
+ }
+ } catch (IOException e) {if(V){Log.w(TAG,"Exception while extracting header",e);}}
+ }
+ }
+
public boolean isValidBody() {
return mHasBody;
}
@@ -274,17 +354,19 @@ public final class ServerOperation implements Operation, BaseStream {
/**
* Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it
- * will wait for a response from the client before ending.
+ * will wait for a response from the client before ending unless SRM is active.
* @param type the response code to send back to the client
* @return <code>true</code> if the final bit was not set on the reply;
* <code>false</code> if no reply was received because the operation
- * ended, an abort was received, or the final bit was set in the
- * reply
+ * ended, an abort was received, the final bit was set in the
+ * reply or SRM is active.
* @throws IOException if an IO error occurs
*/
public synchronized boolean sendReply(int type) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
- int bytesReceived;
+ boolean skipSend = false;
+ boolean skipReceive = false;
+ boolean srmRespSendPending = false;
long id = mListener.getConnectionId();
if (id == -1) {
@@ -293,7 +375,19 @@ public final class ServerOperation implements Operation, BaseStream {
replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
}
- byte[] headerArray = ObexHelper.createHeader(replyHeader, true);
+ if(mSrmEnabled && !mSrmResponseSent) {
+ // As we are not ensured that the SRM enable is in the first OBEX packet
+ // We must check for each reply.
+ if(V)Log.v(TAG, "mSrmEnabled==true, sending SRM enable response.");
+ replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRM_ENABLE);
+ srmRespSendPending = true;
+ }
+
+ if(mSrmEnabled && !mGetOperation && mSrmLocalWait) {
+ replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRMP_WAIT);
+ }
+
+ byte[] headerArray = ObexHelper.createHeader(replyHeader, true); // This clears the headers
int bodyLength = -1;
int orginalBodyLength = -1;
@@ -347,6 +441,28 @@ public final class ServerOperation implements Operation, BaseStream {
finalBitSet = true;
}
+ if(mSrmActive) {
+ if(!mGetOperation && type == ResponseCodes.OBEX_HTTP_CONTINUE &&
+ mSrmResponseSent == true) {
+ // we are in the middle of a SRM PUT operation, don't send a continue.
+ skipSend = true;
+ } else if(mGetOperation && mRequestFinished == false && mSrmResponseSent == true) {
+ // We are still receiving the get request, receive, but don't send continue.
+ skipSend = true;
+ } else if(mGetOperation && mRequestFinished == true) {
+ // All done receiving the GET request, send data to the client, without
+ // expecting a continue.
+ skipReceive = true;
+ }
+ if(V)Log.v(TAG, "type==" + type + " skipSend==" + skipSend
+ + " skipReceive==" + skipReceive);
+ }
+ if(srmRespSendPending) {
+ if(V)Log.v(TAG,
+ "SRM Enabled (srmRespSendPending == true)- sending SRM Enable response");
+ mSrmResponseSent = true;
+ }
+
if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
if (bodyLength > 0) {
/*
@@ -387,7 +503,7 @@ public final class ServerOperation implements Operation, BaseStream {
}
if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
- if(mSendBodyHeader == true) {
+ if(mSendBodyHeader) {
out.write(0x49);
orginalBodyLength = 3;
out.write((byte)(orginalBodyLength >> 8));
@@ -395,107 +511,66 @@ public final class ServerOperation implements Operation, BaseStream {
}
}
- mResponseSize = 3;
- mParent.sendResponse(type, out.toByteArray());
+ if(skipSend == false) {
+ mResponseSize = 3;
+ mParent.sendResponse(type, out.toByteArray());
+ }
if (type == ResponseCodes.OBEX_HTTP_CONTINUE) {
- int headerID = mInput.read();
- int length = mInput.read();
- length = (length << 8) + mInput.read();
- if ((headerID != ObexHelper.OBEX_OPCODE_PUT)
- && (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL)
- && (headerID != ObexHelper.OBEX_OPCODE_GET)
- && (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
-
- if (length > 3) {
- byte[] temp = new byte[length - 3];
- // First three bytes already read, compensating for this
- bytesReceived = mInput.read(temp);
-
- while (bytesReceived != temp.length) {
- bytesReceived += mInput.read(temp, bytesReceived,
- temp.length - bytesReceived);
- }
- }
- /*
- * Determine if an ABORT was sent as the reply
- */
- if (headerID == ObexHelper.OBEX_OPCODE_ABORT) {
- mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
- mClosed = true;
- isAborted = true;
- mExceptionString = "Abort Received";
- throw new IOException("Abort Received");
- } else {
- mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
- mClosed = true;
- mExceptionString = "Bad Request Received";
- throw new IOException("Bad Request Received");
- }
+ if(mGetOperation && skipReceive) {
+ // Here we need to check for and handle abort (throw an exception).
+ // Any other signal received should be discarded silently (only on server side)
+ checkSrmRemoteAbort();
} else {
-
- if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
- finalBitSet = true;
- } else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
- mRequestFinished = true;
- }
-
- /*
- * Determine if the packet length is larger then this device can receive
- */
- if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
- mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
- throw new IOException("Packet received was too large");
- }
-
- /*
- * Determine if any headers were sent in the initial request
- */
- if (length > 3) {
- byte[] data = new byte[length - 3];
- bytesReceived = mInput.read(data);
-
- while (bytesReceived != data.length) {
- bytesReceived += mInput.read(data, bytesReceived, data.length
- - bytesReceived);
- }
- byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
- if (body != null) {
- mHasBody = true;
- }
- if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
- mListener.setConnectionId(ObexHelper
- .convertToLong(requestHeader.mConnectionID));
+ // Receive and handle data (only send reply if !skipSend)
+ // Read a complete OBEX Packet
+ ObexPacket packet = ObexPacket.read(mInput);
+
+ int headerId = packet.mHeaderId;
+ if ((headerId != ObexHelper.OBEX_OPCODE_PUT)
+ && (headerId != ObexHelper.OBEX_OPCODE_PUT_FINAL)
+ && (headerId != ObexHelper.OBEX_OPCODE_GET)
+ && (headerId != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
+
+ /*
+ * Determine if an ABORT was sent as the reply
+ */
+ if (headerId == ObexHelper.OBEX_OPCODE_ABORT) {
+ handleRemoteAbort();
} else {
- mListener.setConnectionId(1);
+ // TODO:shall we send this if it occurs during SRM? Errata on the subject
+ mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
+ mClosed = true;
+ mExceptionString = "Bad Request Received";
+ throw new IOException("Bad Request Received");
}
+ } else {
- if (requestHeader.mAuthResp != null) {
- if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
- mExceptionString = "Authentication Failed";
- mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
- mClosed = true;
- requestHeader.mAuthResp = null;
- return false;
- }
- requestHeader.mAuthResp = null;
+ if ((headerId == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
+ finalBitSet = true;
+ } else if (headerId == ObexHelper.OBEX_OPCODE_GET_FINAL) {
+ mRequestFinished = true;
}
- if (requestHeader.mAuthChall != null) {
- mParent.handleAuthChall(requestHeader);
- // send the auhtResp to the client
- replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
- System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
- replyHeader.mAuthResp.length);
- requestHeader.mAuthResp = null;
- requestHeader.mAuthChall = null;
+ /*
+ * Determine if the packet length is larger than the negotiated packet size
+ */
+ if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
+ mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
+ throw new IOException("Packet received was too large");
}
- if (body != null) {
- mPrivateInput.writeBytes(body, 1);
+ /*
+ * Determine if any headers were sent in the initial request
+ */
+ if (packet.mLength > 3 || (mSrmEnabled && packet.mLength == 3)) {
+ if(handleObexPacket(packet) == false) {
+ return false;
+ }
}
}
+
}
return true;
} else {
@@ -504,6 +579,53 @@ public final class ServerOperation implements Operation, BaseStream {
}
/**
+ * This method will look for an abort from the peer during a SRM transfer.
+ * The function will not block if no data has been received from the remote device.
+ * If data have been received, the function will block while reading the incoming
+ * OBEX package.
+ * An Abort request will be handled, and cause an IOException("Abort Received").
+ * Other messages will be discarded silently as per GOEP specification.
+ * @throws IOException if an abort request have been received.
+ * TODO: I think this is an error in the specification. If we discard other messages,
+ * the peer device will most likely stall, as it will not receive the expected
+ * response for the message...
+ * I'm not sure how to understand "Receipt of invalid or unexpected SRM or SRMP
+ * header values shall be ignored by the receiving device."
+ * If any signal is received during an active SRM transfer it is unexpected regardless
+ * whether or not it contains SRM/SRMP headers...
+ */
+ private void checkSrmRemoteAbort() throws IOException {
+ if(mInput.available() > 0) {
+ ObexPacket packet = ObexPacket.read(mInput);
+ /*
+ * Determine if an ABORT was sent as the reply
+ */
+ if (packet.mHeaderId == ObexHelper.OBEX_OPCODE_ABORT) {
+ handleRemoteAbort();
+ } else {
+ // TODO: should we throw an exception here anyway? - don't see how to
+ // ignore SRM/SRMP headers without ignoring the complete signal
+ // (in this particular case).
+ Log.w(TAG, "Received unexpected request from client - discarding...\n"
+ + " headerId: " + packet.mHeaderId + " length: " + packet.mLength);
+ }
+ }
+ }
+
+ private void handleRemoteAbort() throws IOException {
+ /* TODO: To increase the speed of the abort operation in SRM, we need
+ * to be able to flush the L2CAP queue for the PSM in use.
+ * This could be implemented by introducing a control
+ * message to be send over the socket, that in the abort case
+ * could carry a flush command. */
+ mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
+ mClosed = true;
+ isAborted = true;
+ mExceptionString = "Abort Received";
+ throw new IOException("Abort Received");
+ }
+
+ /**
* Sends an ABORT message to the server. By calling this method, the
* corresponding input and output streams will be closed along with this
* object.
diff --git a/obex/javax/obex/ServerRequestHandler.java b/obex/javax/obex/ServerRequestHandler.java
index 08825729949e..09cbc2ca489f 100644
--- a/obex/javax/obex/ServerRequestHandler.java
+++ b/obex/javax/obex/ServerRequestHandler.java
@@ -275,4 +275,13 @@ public class ServerRequestHandler {
*/
public void onClose() {
}
+
+ /**
+ * Override to add Single Response Mode support - e.g. if the supplied
+ * transport is l2cap.
+ * @return True if SRM is supported, else False
+ */
+ public boolean isSrmSupported() {
+ return false;
+ }
}
diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java
index f1b9a0d6c4d6..acee5ddd5f8f 100644
--- a/obex/javax/obex/ServerSession.java
+++ b/obex/javax/obex/ServerSession.java
@@ -1,4 +1,6 @@
/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (c) 2015 Samsung LSI
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
@@ -45,6 +47,7 @@ import java.io.OutputStream;
public final class ServerSession extends ObexSession implements Runnable {
private static final String TAG = "Obex ServerSession";
+ private static final boolean V = ObexHelper.VDBG;
private ObexTransport mTransport;
@@ -91,7 +94,9 @@ public final class ServerSession extends ObexSession implements Runnable {
boolean done = false;
while (!done && !mClosed) {
+ if(V) Log.v(TAG, "Waiting for incoming request...");
int requestType = mInput.read();
+ if(V) Log.v(TAG, "Read request: " + requestType);
switch (requestType) {
case ObexHelper.OBEX_OPCODE_CONNECT:
handleConnectRequest();
@@ -140,9 +145,9 @@ public final class ServerSession extends ObexSession implements Runnable {
}
} catch (NullPointerException e) {
- Log.d(TAG, e.toString());
+ Log.d(TAG, "Exception occured - ignoring", e);
} catch (Exception e) {
- Log.d(TAG, e.toString());
+ Log.d(TAG, "Exception occured - ignoring", e);
}
close();
}
@@ -163,7 +168,7 @@ public final class ServerSession extends ObexSession implements Runnable {
int length = mInput.read();
length = (length << 8) + mInput.read();
- if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
+ if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
} else {
for (int i = 3; i < length; i++) {
@@ -215,6 +220,7 @@ public final class ServerSession extends ObexSession implements Runnable {
*internal error should not be sent because server has already replied with
*OK response in "sendReply")
*/
+ if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
if (!op.isAborted) {
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
}
@@ -243,6 +249,7 @@ public final class ServerSession extends ObexSession implements Runnable {
op.sendReply(response);
}
} catch (Exception e) {
+ if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
}
}
@@ -275,7 +282,7 @@ public final class ServerSession extends ObexSession implements Runnable {
data[2] = (byte)totalLength;
}
op.write(data);
- op.flush();
+ op.flush(); // TODO: Do we need to flush?
}
/**
@@ -304,7 +311,7 @@ public final class ServerSession extends ObexSession implements Runnable {
flags = mInput.read();
constants = mInput.read();
- if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
+ if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
totalLength = 3;
} else {
@@ -358,6 +365,7 @@ public final class ServerSession extends ObexSession implements Runnable {
try {
code = mListener.onSetPath(request, reply, backup, create);
} catch (Exception e) {
+ if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
return;
}
@@ -425,7 +433,7 @@ public final class ServerSession extends ObexSession implements Runnable {
length = mInput.read();
length = (length << 8) + mInput.read();
- if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
+ if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
totalLength = 3;
} else {
@@ -466,6 +474,7 @@ public final class ServerSession extends ObexSession implements Runnable {
try {
mListener.onDisconnect(request, reply);
} catch (Exception e) {
+ if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
return;
}
@@ -531,23 +540,38 @@ public final class ServerSession extends ObexSession implements Runnable {
HeaderSet reply = new HeaderSet();
int bytesReceived;
+ if(V) Log.v(TAG,"handleConnectRequest()");
+
/*
* Read in the length of the OBEX packet, OBEX version, flags, and max
* packet length
*/
packetLength = mInput.read();
packetLength = (packetLength << 8) + mInput.read();
+ if(V) Log.v(TAG,"handleConnectRequest() - packetLength: " + packetLength);
+
version = mInput.read();
flags = mInput.read();
mMaxPacketLength = mInput.read();
mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read();
+ if(V) Log.v(TAG,"handleConnectRequest() - version: " + version
+ + " MaxLength: " + mMaxPacketLength + " flags: " + flags);
+
// should we check it?
if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) {
mMaxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT;
}
- if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) {
+ if(mMaxPacketLength > ObexHelper.getMaxTxPacketSize(mTransport)) {
+ Log.w(TAG, "Requested MaxObexPacketSize " + mMaxPacketLength
+ + " is larger than the max size supported by the transport: "
+ + ObexHelper.getMaxTxPacketSize(mTransport)
+ + " Reducing to this size.");
+ mMaxPacketLength = ObexHelper.getMaxTxPacketSize(mTransport);
+ }
+
+ if (packetLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
totalLength = 7;
} else {
@@ -614,7 +638,7 @@ public final class ServerSession extends ObexSession implements Runnable {
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
} catch (Exception e) {
- e.printStackTrace();
+ if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
totalLength = 7;
head = null;
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
@@ -633,13 +657,14 @@ public final class ServerSession extends ObexSession implements Runnable {
* Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers
*/
byte[] sendData = new byte[totalLength];
+ int maxRxLength = ObexHelper.getMaxRxPacketSize(mTransport);
sendData[0] = (byte)code;
sendData[1] = length[2];
sendData[2] = length[3];
sendData[3] = (byte)0x10;
sendData[4] = (byte)0x00;
- sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8);
- sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF);
+ sendData[5] = (byte)(maxRxLength >> 8);
+ sendData[6] = (byte)(maxRxLength & 0xFF);
if (head != null) {
System.arraycopy(head, 0, sendData, 7, head.length);
@@ -659,11 +684,16 @@ public final class ServerSession extends ObexSession implements Runnable {
mListener.onClose();
}
try {
- mInput.close();
- mOutput.close();
- mTransport.close();
+ /* Set state to closed before interrupting the thread by closing the streams */
mClosed = true;
+ if(mInput != null)
+ mInput.close();
+ if(mOutput != null)
+ mOutput.close();
+ if(mTransport != null)
+ mTransport.close();
} catch (Exception e) {
+ if(V) Log.d(TAG,"Exception occured during close() - ignore",e);
}
mTransport = null;
mInput = null;
@@ -702,4 +732,7 @@ public final class ServerSession extends ObexSession implements Runnable {
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
+ public ObexTransport getTransport() {
+ return mTransport;
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index d2ef3d785e05..a9f03b6b99e8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -191,7 +191,8 @@ public class CopyService extends IntentService {
cancelIntent.putExtra(EXTRA_CANCEL, mJobId);
mProgressBuilder.addAction(R.drawable.ic_cab_cancel,
getString(android.R.string.cancel), PendingIntent.getService(this, 0,
- cancelIntent, PendingIntent.FLAG_ONE_SHOT));
+ cancelIntent,
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT));
// Send an initial progress notification.
mProgressBuilder.setProgress(0, 0, true); // Indeterminate progress while setting up.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index 5e565bfccbeb..7ea51b970e50 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -97,7 +97,6 @@ public class PickFragment extends Fragment {
CharSequence displayName) {
if (mContainer != null) {
if (pickTarget != null) {
- mContainer.setVisibility(View.VISIBLE);
final Locale locale = getResources().getConfiguration().locale;
switch (action) {
case BaseActivity.State.ACTION_OPEN_TREE:
@@ -112,7 +111,9 @@ public class PickFragment extends Fragment {
default:
throw new IllegalArgumentException("Illegal action for PickFragment.");
}
-
+ }
+ if (pickTarget != null && pickTarget.isCreateSupported()) {
+ mContainer.setVisibility(View.VISIBLE);
} else {
mContainer.setVisibility(View.GONE);
}
diff --git a/packages/SystemUI/res/anim/ic_qs_signal_blink_1.xml b/packages/SystemUI/res/anim/ic_qs_signal_blink_1.xml
new file mode 100644
index 000000000000..57b61da4dfab
--- /dev/null
+++ b/packages/SystemUI/res/anim/ic_qs_signal_blink_1.xml
@@ -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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="@integer/carrier_network_change_anim_time"
+ android:repeatCount="-1">
+
+ <propertyValuesHolder
+ android:propertyName="fillColor"
+ android:valueType="colorType">
+ <keyframe
+ android:fraction="0.0"
+ android:value="#FFFFFFFF"/>
+ <keyframe
+ android:fraction="0.32"
+ android:value="#FFFFFFFF"/>
+ <keyframe
+ android:fraction="0.33"
+ android:value="#4DFFFFFF"/>
+ <keyframe
+ android:fraction="1.0"
+ android:value="#4DFFFFFF"/>
+ </propertyValuesHolder>
+
+</objectAnimator>
diff --git a/packages/SystemUI/res/anim/ic_qs_signal_blink_2.xml b/packages/SystemUI/res/anim/ic_qs_signal_blink_2.xml
new file mode 100644
index 000000000000..09694c3ec308
--- /dev/null
+++ b/packages/SystemUI/res/anim/ic_qs_signal_blink_2.xml
@@ -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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="@integer/carrier_network_change_anim_time"
+ android:repeatCount="-1">
+
+ <propertyValuesHolder
+ android:propertyName="fillColor"
+ android:valueType="colorType">
+ <keyframe
+ android:fraction="0.0"
+ android:value="#4DFFFFFF"/>
+ <keyframe
+ android:fraction="0.32"
+ android:value="#4DFFFFFF"/>
+ <keyframe
+ android:fraction="0.33"
+ android:value="#FFFFFFFF"/>
+ <keyframe
+ android:fraction="0.66"
+ android:value="#FFFFFFFF"/>
+ <keyframe
+ android:fraction="0.67"
+ android:value="#4DFFFFFF"/>
+ <keyframe
+ android:fraction="1.0"
+ android:value="#4DFFFFFF"/>
+ </propertyValuesHolder>
+
+</objectAnimator>
diff --git a/packages/SystemUI/res/anim/ic_qs_signal_blink_3.xml b/packages/SystemUI/res/anim/ic_qs_signal_blink_3.xml
new file mode 100644
index 000000000000..2270e3f9ce9d
--- /dev/null
+++ b/packages/SystemUI/res/anim/ic_qs_signal_blink_3.xml
@@ -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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="@integer/carrier_network_change_anim_time"
+ android:repeatCount="-1">
+
+ <propertyValuesHolder
+ android:propertyName="fillColor"
+ android:valueType="colorType">
+ <keyframe
+ android:fraction="0.0"
+ android:value="#4DFFFFFF"/>
+ <keyframe
+ android:fraction="0.66"
+ android:value="#4DFFFFFF"/>
+ <keyframe
+ android:fraction="0.67"
+ android:value="#FFFFFFFF"/>
+ <keyframe
+ android:fraction="1.0"
+ android:value="#FFFFFFFF"/>
+ </propertyValuesHolder>
+
+</objectAnimator>
diff --git a/packages/SystemUI/res/anim/ic_signal_blink_1.xml b/packages/SystemUI/res/anim/ic_signal_blink_1.xml
new file mode 100644
index 000000000000..ab1905af4e76
--- /dev/null
+++ b/packages/SystemUI/res/anim/ic_signal_blink_1.xml
@@ -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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="@integer/carrier_network_change_anim_time"
+ android:repeatCount="-1">
+
+ <propertyValuesHolder
+ android:propertyName="fillColor"
+ android:valueType="colorType">
+ <keyframe
+ android:fraction="0.0"
+ android:value="@color/light_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="0.32"
+ android:value="@color/light_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="0.33"
+ android:value="@color/light_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="1.0"
+ android:value="@color/light_mode_icon_color_dual_tone_background"/>
+ </propertyValuesHolder>
+
+</objectAnimator>
diff --git a/packages/SystemUI/res/anim/ic_signal_blink_2.xml b/packages/SystemUI/res/anim/ic_signal_blink_2.xml
new file mode 100644
index 000000000000..1b7ace252ea3
--- /dev/null
+++ b/packages/SystemUI/res/anim/ic_signal_blink_2.xml
@@ -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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="@integer/carrier_network_change_anim_time"
+ android:repeatCount="-1">
+
+ <propertyValuesHolder
+ android:propertyName="fillColor"
+ android:valueType="colorType">
+ <keyframe
+ android:fraction="0.0"
+ android:value="@color/light_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="0.32"
+ android:value="@color/light_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="0.33"
+ android:value="@color/light_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="0.66"
+ android:value="@color/light_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="0.67"
+ android:value="@color/light_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="1.0"
+ android:value="@color/light_mode_icon_color_dual_tone_background"/>
+ </propertyValuesHolder>
+
+</objectAnimator>
diff --git a/packages/SystemUI/res/anim/ic_signal_blink_3.xml b/packages/SystemUI/res/anim/ic_signal_blink_3.xml
new file mode 100644
index 000000000000..cee831c0d7d3
--- /dev/null
+++ b/packages/SystemUI/res/anim/ic_signal_blink_3.xml
@@ -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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="@integer/carrier_network_change_anim_time"
+ android:repeatCount="-1">
+
+ <propertyValuesHolder
+ android:propertyName="fillColor"
+ android:valueType="colorType">
+ <keyframe
+ android:fraction="0.0"
+ android:value="@color/light_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="0.66"
+ android:value="@color/light_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="0.67"
+ android:value="@color/light_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="1.0"
+ android:value="@color/light_mode_icon_color_dual_tone_fill"/>
+ </propertyValuesHolder>
+
+</objectAnimator>
diff --git a/packages/SystemUI/res/anim/ic_signal_dark_blink_1.xml b/packages/SystemUI/res/anim/ic_signal_dark_blink_1.xml
new file mode 100644
index 000000000000..9d398faad706
--- /dev/null
+++ b/packages/SystemUI/res/anim/ic_signal_dark_blink_1.xml
@@ -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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="@integer/carrier_network_change_anim_time"
+ android:repeatCount="-1">
+
+ <propertyValuesHolder
+ android:propertyName="fillColor"
+ android:valueType="colorType">
+ <keyframe
+ android:fraction="0.0"
+ android:value="@color/dark_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="0.32"
+ android:value="@color/dark_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="0.33"
+ android:value="@color/dark_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="1.0"
+ android:value="@color/dark_mode_icon_color_dual_tone_background"/>
+ </propertyValuesHolder>
+
+</objectAnimator>
diff --git a/packages/SystemUI/res/anim/ic_signal_dark_blink_2.xml b/packages/SystemUI/res/anim/ic_signal_dark_blink_2.xml
new file mode 100644
index 000000000000..c6e213d2848a
--- /dev/null
+++ b/packages/SystemUI/res/anim/ic_signal_dark_blink_2.xml
@@ -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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="@integer/carrier_network_change_anim_time"
+ android:repeatCount="-1">
+
+ <propertyValuesHolder
+ android:propertyName="fillColor"
+ android:valueType="colorType">
+ <keyframe
+ android:fraction="0.0"
+ android:value="@color/dark_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="0.32"
+ android:value="@color/dark_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="0.33"
+ android:value="@color/dark_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="0.66"
+ android:value="@color/dark_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="0.67"
+ android:value="@color/dark_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="1.0"
+ android:value="@color/dark_mode_icon_color_dual_tone_background"/>
+ </propertyValuesHolder>
+
+</objectAnimator>
diff --git a/packages/SystemUI/res/anim/ic_signal_dark_blink_3.xml b/packages/SystemUI/res/anim/ic_signal_dark_blink_3.xml
new file mode 100644
index 000000000000..dce148cf4100
--- /dev/null
+++ b/packages/SystemUI/res/anim/ic_signal_dark_blink_3.xml
@@ -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.
+-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="@integer/carrier_network_change_anim_time"
+ android:repeatCount="-1">
+
+ <propertyValuesHolder
+ android:propertyName="fillColor"
+ android:valueType="colorType">
+ <keyframe
+ android:fraction="0.0"
+ android:value="@color/dark_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="0.66"
+ android:value="@color/dark_mode_icon_color_dual_tone_background"/>
+ <keyframe
+ android:fraction="0.67"
+ android:value="@color/dark_mode_icon_color_dual_tone_fill"/>
+ <keyframe
+ android:fraction="1.0"
+ android:value="@color/dark_mode_icon_color_dual_tone_fill"/>
+ </propertyValuesHolder>
+
+</objectAnimator>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
new file mode 100644
index 000000000000..96e2fd4e5f82
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:name="dot1"
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M9.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/>
+ <path
+ android:name="dot2"
+ android:fillColor="#4DFFFFFF"
+ android:pathData="M14.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/>
+ <path
+ android:name="dot3"
+ android:fillColor="#4DFFFFFF"
+ android:pathData="M19.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/>
+ <path
+ android:fillColor="#4DFFFFFF"
+ android:pathData="M2.0,22.0l6.0,0.0 0.0,-4.0 14.0,0.0 0.0,-16.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change_animation.xml b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change_animation.xml
new file mode 100644
index 000000000000..2186aa82e83f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change_animation.xml
@@ -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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/ic_qs_signal_carrier_network_change" >
+ <target
+ android:name="dot1"
+ android:animation="@anim/ic_qs_signal_blink_1"/>
+ <target
+ android:name="dot2"
+ android:animation="@anim/ic_qs_signal_blink_2"/>
+ <target
+ android:name="dot3"
+ android:animation="@anim/ic_qs_signal_blink_3"/>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change.xml b/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change.xml
new file mode 100644
index 000000000000..f69ffe49b138
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="17dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:name="dot1"
+ android:fillColor="?attr/fillColor"
+ android:pathData="M9.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/>
+ <path
+ android:name="dot2"
+ android:fillColor="?attr/backgroundColor"
+ android:pathData="M14.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/>
+ <path
+ android:name="dot3"
+ android:fillColor="?attr/backgroundColor"
+ android:pathData="M19.0,19.0l3.0,0.0l0.0,3.0l-3.0,0.0z"/>
+ <path
+ android:fillColor="?attr/backgroundColor"
+ android:pathData="M2.0,22.0l6.0,0.0 0.0,-4.0 14.0,0.0 0.0,-16.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change_animation.xml b/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change_animation.xml
new file mode 100644
index 000000000000..275f037778c0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_carrier_network_change_animation.xml
@@ -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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/stat_sys_signal_carrier_network_change" >
+ <target
+ android:name="dot1"
+ android:animation="@anim/ic_signal_blink_1"/>
+ <target
+ android:name="dot2"
+ android:animation="@anim/ic_signal_blink_2"/>
+ <target
+ android:name="dot3"
+ android:animation="@anim/ic_signal_blink_3"/>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_dark_carrier_network_change_animation.xml b/packages/SystemUI/res/drawable/stat_sys_signal_dark_carrier_network_change_animation.xml
new file mode 100644
index 000000000000..ff49d4cfdb13
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_dark_carrier_network_change_animation.xml
@@ -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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/stat_sys_signal_carrier_network_change" >
+ <target
+ android:name="dot1"
+ android:animation="@anim/ic_signal_dark_blink_1"/>
+ <target
+ android:name="dot2"
+ android:animation="@anim/ic_signal_dark_blink_2"/>
+ <target
+ android:name="dot3"
+ android:animation="@anim/ic_signal_dark_blink_3"/>
+</animated-vector>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index bba8a6da27db..1f13404890ee 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Weier"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is die volumedialoog"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Raak om die oorspronklike terug te stel."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen-kenmerk"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 06e9fb232ef4..a631b9051540 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"ከልክል"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> የድምጽ መጠን መገናኛው ነው"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"የመጀመሪያውን ወደነበረበት ለመመለስ ይንኩ።"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index ac86dbda6a3c..dfaf4087a216 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -403,7 +403,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"رفض"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> هو مربع حوار مستوى الصوت"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"المس لاستعادة الإعداد الأصلي."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 64e5afafc040..6bd996956884 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Отказване"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> изпълнява ролята на диалоговия прозорец за силата на звука"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Докоснете, за да възстановите оригинала."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-bn-rBD/strings.xml b/packages/SystemUI/res/values-bn-rBD/strings.xml
index dd83d59258ab..8f057e49e051 100644
--- a/packages/SystemUI/res/values-bn-rBD/strings.xml
+++ b/packages/SystemUI/res/values-bn-rBD/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"প্রত্যাখ্যান করুন"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> হল ভলিউম ডায়লগ"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"আসলটি পুনঃস্থাপন করতে স্পর্শ করুন৷"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index eda024d74258..706086a5b0b4 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Denega"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> és el diàleg de volum"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Toca per restaurar l\'original."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 7abcc11d4ae8..de7c62a7c77d 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -403,7 +403,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Odmítnout"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> je dialog hlasitosti"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Klepnutím obnovíte originál."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 3d0a0b04a730..99fc26415396 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Afvis"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> er dialogboksen for lydstyrke"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Tryk for at gendanne originalen."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index f77da1220497..eb0ea2cfdc9c 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Ablehnen"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> regelt die Lautstärke."</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Zum Wiederherstellen des Originals hier tippen"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index e1e8ba30a424..e4c85c188075 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Απόρριψη"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> αποτελεί το παράθυρο διαλόγου ελέγχου έντασης"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Αγγίξτε για επαναφορά αρχικού."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 9e76616121f0..aadd011e1920 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -167,8 +167,7 @@
<string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Lock screen."</string>
<string name="accessibility_desc_settings" msgid="3417884241751434521">"Settings"</string>
<string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Overview."</string>
- <!-- no translation found for accessibility_desc_confirm (3446792278337969766) -->
- <skip />
+ <string name="accessibility_desc_confirm" msgid="3446792278337969766">"Confirm"</string>
<string name="accessibility_quick_settings_user" msgid="1104846699869476855">"User <xliff:g id="USER">%s</xliff:g>."</string>
<string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string>
<string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"Wi-Fi turned off."</string>
@@ -303,10 +302,8 @@
<string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="zen_no_interruptions_with_warning" msgid="4396898053735625287">"No interruptions. Not even alarms."</string>
- <!-- no translation found for zen_priority_introduction (7253045784560169993) -->
- <skip />
- <!-- no translation found for zen_priority_customize_button (7948043278226955063) -->
- <skip />
+ <string name="zen_priority_introduction" msgid="7253045784560169993">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify."</string>
+ <string name="zen_priority_customize_button" msgid="7948043278226955063">"Customise"</string>
<string name="zen_no_interruptions" msgid="7970973750143632592">"No interruptions"</string>
<string name="zen_important_interruptions" msgid="3477041776609757628">"Priority interruptions only"</string>
<string name="zen_alarms" msgid="5055668280767657759">"Alarms only"</string>
@@ -342,12 +339,9 @@
<string name="guest_wipe_session_message" msgid="8476238178270112811">"Do you want to continue your session?"</string>
<string name="guest_wipe_session_wipe" msgid="5065558566939858884">"Start again"</string>
<string name="guest_wipe_session_dontwipe" msgid="1401113462524894716">"Yes, continue"</string>
- <!-- no translation found for guest_notification_title (1585278533840603063) -->
- <skip />
- <!-- no translation found for guest_notification_text (7513706222848825467) -->
- <skip />
- <!-- no translation found for guest_notification_remove_action (8820670703892101990) -->
- <skip />
+ <string name="guest_notification_title" msgid="1585278533840603063">"Guest user"</string>
+ <string name="guest_notification_text" msgid="7513706222848825467">"Remove guest to delete apps and data"</string>
+ <string name="guest_notification_remove_action" msgid="8820670703892101990">"REMOVE GUEST"</string>
<string name="user_add_user_title" msgid="4553596395824132638">"Add new user?"</string>
<string name="user_add_user_message_short" msgid="2161624834066214559">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string>
<string name="battery_saver_notification_title" msgid="237918726750955859">"Battery saver is on"</string>
@@ -399,7 +393,5 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Deny"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is the volume dialogue"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Touch to restore the original."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
- <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
- <skip />
+ <string name="managed_profile_foreground_toast" msgid="3199278359979281097">"You are in the Work profile"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 9e76616121f0..aadd011e1920 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -167,8 +167,7 @@
<string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Lock screen."</string>
<string name="accessibility_desc_settings" msgid="3417884241751434521">"Settings"</string>
<string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Overview."</string>
- <!-- no translation found for accessibility_desc_confirm (3446792278337969766) -->
- <skip />
+ <string name="accessibility_desc_confirm" msgid="3446792278337969766">"Confirm"</string>
<string name="accessibility_quick_settings_user" msgid="1104846699869476855">"User <xliff:g id="USER">%s</xliff:g>."</string>
<string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string>
<string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"Wi-Fi turned off."</string>
@@ -303,10 +302,8 @@
<string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="zen_no_interruptions_with_warning" msgid="4396898053735625287">"No interruptions. Not even alarms."</string>
- <!-- no translation found for zen_priority_introduction (7253045784560169993) -->
- <skip />
- <!-- no translation found for zen_priority_customize_button (7948043278226955063) -->
- <skip />
+ <string name="zen_priority_introduction" msgid="7253045784560169993">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify."</string>
+ <string name="zen_priority_customize_button" msgid="7948043278226955063">"Customise"</string>
<string name="zen_no_interruptions" msgid="7970973750143632592">"No interruptions"</string>
<string name="zen_important_interruptions" msgid="3477041776609757628">"Priority interruptions only"</string>
<string name="zen_alarms" msgid="5055668280767657759">"Alarms only"</string>
@@ -342,12 +339,9 @@
<string name="guest_wipe_session_message" msgid="8476238178270112811">"Do you want to continue your session?"</string>
<string name="guest_wipe_session_wipe" msgid="5065558566939858884">"Start again"</string>
<string name="guest_wipe_session_dontwipe" msgid="1401113462524894716">"Yes, continue"</string>
- <!-- no translation found for guest_notification_title (1585278533840603063) -->
- <skip />
- <!-- no translation found for guest_notification_text (7513706222848825467) -->
- <skip />
- <!-- no translation found for guest_notification_remove_action (8820670703892101990) -->
- <skip />
+ <string name="guest_notification_title" msgid="1585278533840603063">"Guest user"</string>
+ <string name="guest_notification_text" msgid="7513706222848825467">"Remove guest to delete apps and data"</string>
+ <string name="guest_notification_remove_action" msgid="8820670703892101990">"REMOVE GUEST"</string>
<string name="user_add_user_title" msgid="4553596395824132638">"Add new user?"</string>
<string name="user_add_user_message_short" msgid="2161624834066214559">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string>
<string name="battery_saver_notification_title" msgid="237918726750955859">"Battery saver is on"</string>
@@ -399,7 +393,5 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Deny"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is the volume dialogue"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Touch to restore the original."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
- <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
- <skip />
+ <string name="managed_profile_foreground_toast" msgid="3199278359979281097">"You are in the Work profile"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 9e76616121f0..aadd011e1920 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -167,8 +167,7 @@
<string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Lock screen."</string>
<string name="accessibility_desc_settings" msgid="3417884241751434521">"Settings"</string>
<string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Overview."</string>
- <!-- no translation found for accessibility_desc_confirm (3446792278337969766) -->
- <skip />
+ <string name="accessibility_desc_confirm" msgid="3446792278337969766">"Confirm"</string>
<string name="accessibility_quick_settings_user" msgid="1104846699869476855">"User <xliff:g id="USER">%s</xliff:g>."</string>
<string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string>
<string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"Wi-Fi turned off."</string>
@@ -303,10 +302,8 @@
<string name="description_direction_up" msgid="7169032478259485180">"Slide up for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="description_direction_left" msgid="7207478719805562165">"Slide left for <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>."</string>
<string name="zen_no_interruptions_with_warning" msgid="4396898053735625287">"No interruptions. Not even alarms."</string>
- <!-- no translation found for zen_priority_introduction (7253045784560169993) -->
- <skip />
- <!-- no translation found for zen_priority_customize_button (7948043278226955063) -->
- <skip />
+ <string name="zen_priority_introduction" msgid="7253045784560169993">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers that you specify."</string>
+ <string name="zen_priority_customize_button" msgid="7948043278226955063">"Customise"</string>
<string name="zen_no_interruptions" msgid="7970973750143632592">"No interruptions"</string>
<string name="zen_important_interruptions" msgid="3477041776609757628">"Priority interruptions only"</string>
<string name="zen_alarms" msgid="5055668280767657759">"Alarms only"</string>
@@ -342,12 +339,9 @@
<string name="guest_wipe_session_message" msgid="8476238178270112811">"Do you want to continue your session?"</string>
<string name="guest_wipe_session_wipe" msgid="5065558566939858884">"Start again"</string>
<string name="guest_wipe_session_dontwipe" msgid="1401113462524894716">"Yes, continue"</string>
- <!-- no translation found for guest_notification_title (1585278533840603063) -->
- <skip />
- <!-- no translation found for guest_notification_text (7513706222848825467) -->
- <skip />
- <!-- no translation found for guest_notification_remove_action (8820670703892101990) -->
- <skip />
+ <string name="guest_notification_title" msgid="1585278533840603063">"Guest user"</string>
+ <string name="guest_notification_text" msgid="7513706222848825467">"Remove guest to delete apps and data"</string>
+ <string name="guest_notification_remove_action" msgid="8820670703892101990">"REMOVE GUEST"</string>
<string name="user_add_user_title" msgid="4553596395824132638">"Add new user?"</string>
<string name="user_add_user_message_short" msgid="2161624834066214559">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string>
<string name="battery_saver_notification_title" msgid="237918726750955859">"Battery saver is on"</string>
@@ -399,7 +393,5 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Deny"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is the volume dialogue"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Touch to restore the original."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
- <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
- <skip />
+ <string name="managed_profile_foreground_toast" msgid="3199278359979281097">"You are in the Work profile"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 40400f33f667..e75492569bfc 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Rechazar"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> es el cuadro de diálogo de volumen."</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Toca para restaurar el original."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index cb061a5dc847..c1311760df2e 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Rechazar"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> es el cuadro de diálogo de volumen"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Toca para restaurar la versión original."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-et-rEE/strings.xml b/packages/SystemUI/res/values-et-rEE/strings.xml
index 7fffec7647e1..387fe82be03c 100644
--- a/packages/SystemUI/res/values-et-rEE/strings.xml
+++ b/packages/SystemUI/res/values-et-rEE/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Keela"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> on helitugevuse dialoog"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Originaali taastamiseks puudutage."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-eu-rES/strings.xml b/packages/SystemUI/res/values-eu-rES/strings.xml
index 94dd6b169a62..d8c1789db869 100644
--- a/packages/SystemUI/res/values-eu-rES/strings.xml
+++ b/packages/SystemUI/res/values-eu-rES/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Ukatu"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> da bolumenaren leihoa"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Ukitu jatorrizkora leheneratzeko"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 0260d3a0b94d..7594ad9672aa 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"رد کردن"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> کنترل‌کننده صدا است"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"برای بازیابی کنترل‌کننده اصلی، لمس کنید."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"‎@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 156bbb73cd89..a13170140e38 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Estä"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> on äänenvoimakkuusvalinta."</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Palauta alkuperäinen koskettamalla."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index de5b7aa0404d..9d10e48e402d 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Refuser"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> correspond à la boîte de dialogue du volume"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Touchez pour restaurer l\'original."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index ed17e4af395b..2788a87b6971 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Refuser"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> correspond à la boîte de dialogue du volume"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Appuyez pour restaurer l\'interface d\'origine."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-gl-rES/strings.xml b/packages/SystemUI/res/values-gl-rES/strings.xml
index 89d5fc3eacc5..a4786df6d159 100644
--- a/packages/SystemUI/res/values-gl-rES/strings.xml
+++ b/packages/SystemUI/res/values-gl-rES/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Denegar"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> é o cadro de diálogo de volume"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Toca para restaurar o orixinal."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 08fbe696d18c..7d6f51bc23c7 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"अस्वीकार करें"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> वॉल्यूम संवाद है"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"मूल वॉल्यूम को फिर से लाने के लिए स्पर्श करें."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 85bb634a8b0f..7ea2336ca22f 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -400,7 +400,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Odbij"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> predstavlja dijaloški okvir za upravljanje glasnoćom"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Dodirnite da biste vratili izvorno."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 077384962541..f6743271eca4 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Elutasítás"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás kezeli a hangerőt"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Érintse meg az eredeti érték visszaállításához."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-hy-rAM/strings.xml b/packages/SystemUI/res/values-hy-rAM/strings.xml
index 3ffc0306e60f..c3d0f6d3e7cb 100644
--- a/packages/SystemUI/res/values-hy-rAM/strings.xml
+++ b/packages/SystemUI/res/values-hy-rAM/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Մերժել"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ը ձայնի ուժգնության երկխոսության հավելված է"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Դիպչեք՝ սկզբնօրինակը վերականգնելու համար:"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index edee7c6f21cd..f53ceccfe72c 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Tolak"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> adalah dialog volume"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Sentuh untuk memulihkan aslinya."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-is-rIS/strings.xml b/packages/SystemUI/res/values-is-rIS/strings.xml
index 660834eeac9e..8000344d178e 100644
--- a/packages/SystemUI/res/values-is-rIS/strings.xml
+++ b/packages/SystemUI/res/values-is-rIS/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Hafna"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> er hljóðstyrksvalmyndin"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Snertu til að færa í upprunalegt horf."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index a7ac816037ff..a0ceb62c3273 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Nega"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> rappresenta la finestra di dialogo relativa al volume"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Tocca per ripristinare l\'originale."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 837bba7a7422..b02ff0ae628c 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"דחה"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> הוא תיבת הדו-שיח של עוצמת הקול"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"גע כדי לשחזר את עוצמת הקול המקורית."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index e01fe535ef8b..f8f767ba44fb 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"許可しない"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g>を音量ダイアログとして使用"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"タップすると元の音量ダイアログが復元されます。"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ka-rGE/strings.xml b/packages/SystemUI/res/values-ka-rGE/strings.xml
index a41c9f52253b..4377663b38dd 100644
--- a/packages/SystemUI/res/values-ka-rGE/strings.xml
+++ b/packages/SystemUI/res/values-ka-rGE/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"უარყოფა"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ხმოვან დიალოგშია"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"ორიგინალის აღდგენისათვის, შეეხეთ."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-kk-rKZ/strings.xml b/packages/SystemUI/res/values-kk-rKZ/strings.xml
index 0beea99ec82c..c4141e158023 100644
--- a/packages/SystemUI/res/values-kk-rKZ/strings.xml
+++ b/packages/SystemUI/res/values-kk-rKZ/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Өшіру"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> — көлем диалогтық терезесі"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Түпнұсқаны қалпына келтіру үшін түртіңіз."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index ba84a549c3fa..8087373a2058 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"បដិសេធ"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> គឺជាប្រអប់សម្លេង"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"ប៉ះដើម្បីស្តារច្បាប់ដើម។"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-kn-rIN/strings.xml b/packages/SystemUI/res/values-kn-rIN/strings.xml
index 76b57073c250..fe138ce0f4ac 100644
--- a/packages/SystemUI/res/values-kn-rIN/strings.xml
+++ b/packages/SystemUI/res/values-kn-rIN/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"ನಿರಾಕರಿಸು"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ವಾಲ್ಯೂಮ್ ಸಂವಾದವಾಗಿದೆ"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"ಮೂಲ ಮರುಸ್ಥಾಪಿಸಲು ಸ್ಪರ್ಶಿಸಿ."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 32f751f48959..c525095058d4 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"거부"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g>은(는) 볼륨 대화입니다."</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"원본을 복원하려면 터치하세요."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ky-rKG/strings.xml b/packages/SystemUI/res/values-ky-rKG/strings.xml
index 8e2defdcdf38..eca5b2c27b8b 100644
--- a/packages/SystemUI/res/values-ky-rKG/strings.xml
+++ b/packages/SystemUI/res/values-ky-rKG/strings.xml
@@ -424,7 +424,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Жок"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> үндү катуулатуу диалогу"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Түпнусканы калыбына келтирүү үчүн тийип коюңуз."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index d7bb83bace75..8ce9eda7f2ad 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"ປະຕິເສດ"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ແມ່ນ​ໜ້າ​ຕ່າງ​ລະ​ດັບ​ສຽງ"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"ສໍາ​ຜັດ​ເພື່ອກູ້​ຄືນ​ຕົ້ນ​ສະ​ບັບ​."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 2ac46367b722..7e211cd04e1b 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Atmesti"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ yra garsumo valdymo dialogo langas"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Palieskite, kad atkurtumėte originalą."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a184664f8cd7..c6927918788b 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -400,7 +400,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Neatļaut"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ir skaļuma dialoglodziņš"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Pieskarieties, lai atjaunotu sākotnējo."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-mk-rMK/strings.xml b/packages/SystemUI/res/values-mk-rMK/strings.xml
index e2884515dd1f..b13a58a46418 100644
--- a/packages/SystemUI/res/values-mk-rMK/strings.xml
+++ b/packages/SystemUI/res/values-mk-rMK/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Одбиј"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> е дијалог за јачина на звук"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Допрете за да го вратите оригиналот."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ml-rIN/strings.xml b/packages/SystemUI/res/values-ml-rIN/strings.xml
index 3bc27a10a73d..e0d3eaf2e9a3 100644
--- a/packages/SystemUI/res/values-ml-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ml-rIN/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"നിരസിക്കുക"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g>, വോളിയം ഡയലോഗാണ്"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"ആദ്യത്തേത് പുനഃസ്ഥാപിക്കാൻ സ്‌പർശിക്കുക."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-mn-rMN/strings.xml b/packages/SystemUI/res/values-mn-rMN/strings.xml
index 367de5dff3c0..b68af61bf212 100644
--- a/packages/SystemUI/res/values-mn-rMN/strings.xml
+++ b/packages/SystemUI/res/values-mn-rMN/strings.xml
@@ -397,7 +397,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Татгалзах"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> нь дууны диалог юм."</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Анхны хувилбарыг эргүүлэн хадгалахыг хүсвэл хүрнэ үү."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-mr-rIN/strings.xml b/packages/SystemUI/res/values-mr-rIN/strings.xml
index eb8c6adbd478..c9dd37e2f487 100644
--- a/packages/SystemUI/res/values-mr-rIN/strings.xml
+++ b/packages/SystemUI/res/values-mr-rIN/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"नकार द्या"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> हा व्हॉल्यूम संवाद आहे"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"मूळ पुनर्संचयित करण्यासाठी स्पर्श करा."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ms-rMY/strings.xml b/packages/SystemUI/res/values-ms-rMY/strings.xml
index 1b1fded604f1..f5624a0c44c2 100644
--- a/packages/SystemUI/res/values-ms-rMY/strings.xml
+++ b/packages/SystemUI/res/values-ms-rMY/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Tolak"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ialah dialog kelantangan"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Sentuh untuk memulihkan yang asal."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:rentetan/nama_ciri_mod_zen"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-my-rMM/strings.xml b/packages/SystemUI/res/values-my-rMM/strings.xml
index 52c42987bb4a..f0aa3b19af81 100644
--- a/packages/SystemUI/res/values-my-rMM/strings.xml
+++ b/packages/SystemUI/res/values-my-rMM/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"ငြင်းပယ်သည်"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် အသံဒိုင်ယာလော့ခ်ဖြစ်သည်"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"မူရင်းအားပြန်လည်သိမ်းဆည်းရန် ထိပါ။"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 2168f146636a..ef64990b877e 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Ikke tillat"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> er volumdialogen"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Trykk for å gå tilbake til den opprinnelige volumdialogen."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ne-rNP/strings.xml b/packages/SystemUI/res/values-ne-rNP/strings.xml
index 11e19e794c17..1561177d2929 100644
--- a/packages/SystemUI/res/values-ne-rNP/strings.xml
+++ b/packages/SystemUI/res/values-ne-rNP/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"अस्वीकार गर्नुहोस्"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> भोल्यूम संवाद हो"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"मूल पुनर्स्थापना गर्न छुनुहोस्।"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*Android: स्ट्रिङ/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 06ed9867d228..24ace950446b 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Afwijzen"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> is het volumedialoogvenster"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Tik hierop om het origineel te herstellen."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index ffeff1e50380..02c1d7488955 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Odmów"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> steruje głośnością"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Dotknij, by przywrócić pierwotną."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 23413be36ea9..2b802c03a035 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Recusar"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> é a caixa de diálogo do volume"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Toque para restaurar o original."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index caf85ccf7dff..8d82942cc89c 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Negar"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> é a caixa de diálogo referente ao volume"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Toque para restaurar o original."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 509ae926772c..06d9ac201d67 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -400,7 +400,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Refuzați"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> afișează caseta de dialog pentru volum"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Atingeți pentru a reveni la setarea inițială."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 1c584fe2ce46..b6c506f86dd0 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -403,7 +403,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Нет"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"Приложение <xliff:g id="APP_NAME">%1$s</xliff:g> назначено регулятором громкости"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Нажмите, чтобы восстановить приложение по умолчанию."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-si-rLK/strings.xml b/packages/SystemUI/res/values-si-rLK/strings.xml
index 9643102f3950..1e1d981cd72d 100644
--- a/packages/SystemUI/res/values-si-rLK/strings.xml
+++ b/packages/SystemUI/res/values-si-rLK/strings.xml
@@ -167,8 +167,7 @@
<string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"අගුළු තිරය."</string>
<string name="accessibility_desc_settings" msgid="3417884241751434521">"සැකසීම්"</string>
<string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"දළ විශ්ලේෂණය."</string>
- <!-- no translation found for accessibility_desc_confirm (3446792278337969766) -->
- <skip />
+ <string name="accessibility_desc_confirm" msgid="3446792278337969766">"තහවුරු කරන්න"</string>
<string name="accessibility_quick_settings_user" msgid="1104846699869476855">"පරිශීලකයා <xliff:g id="USER">%s</xliff:g>."</string>
<string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string>
<string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"Wifi අක්‍රියයි."</string>
@@ -303,10 +302,8 @@
<string name="description_direction_up" msgid="7169032478259485180">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> සඳහා උඩට සර්පණය කරන්න."</string>
<string name="description_direction_left" msgid="7207478719805562165">"<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> සඳහා වමට සර්පණය කරන්න."</string>
<string name="zen_no_interruptions_with_warning" msgid="4396898053735625287">"අතුරු බිඳීම් නැත. අඩුම තරමේ අනතුරු ඇඟවීමක්වත් නැත."</string>
- <!-- no translation found for zen_priority_introduction (7253045784560169993) -->
- <skip />
- <!-- no translation found for zen_priority_customize_button (7948043278226955063) -->
- <skip />
+ <string name="zen_priority_introduction" msgid="7253045784560169993">"සීනු, සිහි කැඳවීම්, සිදුවීම් සහ ඔබ සඳහන් කරන අමතන්නන් හැර වෙනත් ශබ්ද සහ කම්පන වලින් ඔබව බාධා නොකරයි."</string>
+ <string name="zen_priority_customize_button" msgid="7948043278226955063">"අභිරුචිකරණය"</string>
<string name="zen_no_interruptions" msgid="7970973750143632592">"අතුරු බිදුම් නැත"</string>
<string name="zen_important_interruptions" msgid="3477041776609757628">"ප්‍රමුඛ අතුරු බිඳීම් පමණයි"</string>
<string name="zen_alarms" msgid="5055668280767657759">"ඇඟවීම් පමණි"</string>
@@ -342,12 +339,9 @@
<string name="guest_wipe_session_message" msgid="8476238178270112811">"ඔබගේ සැසිය දිගටම කරගෙන යෑමට ඔබට අවශ්‍යද?"</string>
<string name="guest_wipe_session_wipe" msgid="5065558566939858884">"යළි මුල සිට අරඹන්න"</string>
<string name="guest_wipe_session_dontwipe" msgid="1401113462524894716">"ඔව්, දිගටම කරගෙන යන්න"</string>
- <!-- no translation found for guest_notification_title (1585278533840603063) -->
- <skip />
- <!-- no translation found for guest_notification_text (7513706222848825467) -->
- <skip />
- <!-- no translation found for guest_notification_remove_action (8820670703892101990) -->
- <skip />
+ <string name="guest_notification_title" msgid="1585278533840603063">"ආගන්තුක පරිශිලකයා"</string>
+ <string name="guest_notification_text" msgid="7513706222848825467">"යෙදුම් සහ දත්ත ඉවත් කිරීමට ආගන්තුකයා ඉවත් කරන්න"</string>
+ <string name="guest_notification_remove_action" msgid="8820670703892101990">"ආගන්තුකයා ඉවත් කරන්නද?"</string>
<string name="user_add_user_title" msgid="4553596395824132638">"අලුත් පරිශීලකයෙක් එකතු කරන්නද?"</string>
<string name="user_add_user_message_short" msgid="2161624834066214559">"ඔබ අලුත් පරිශීලකයෙක් එකතු කරන විට, එම පුද්ගලයා ඔහුගේ වැඩ කරන ඉඩ සකසා ගත යුතුය.\n\nසියළුම අනෙක් පරිශීලකයින් සඳහා ඕනෑම පරිශීලකයෙකුට යාවත්කාලීන කළ හැක."</string>
<string name="battery_saver_notification_title" msgid="237918726750955859">"බැටරිය සුරකින්නා සක්‍රීයයි"</string>
@@ -399,7 +393,5 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"ප්‍රතික්ෂේප කරන්න"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ධාරිතා සංවාදයයි"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"මුල් තත්ත්වය නැවත ප්‍රතිසාධනය කිරීමට ස්පර්ශ කරන්න."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
- <!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
- <skip />
+ <string name="managed_profile_foreground_toast" msgid="3199278359979281097">"ඔබ කාර්යාල පැතිකඩේ සිටියි"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index d82690100989..72e30b5a3a21 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -403,7 +403,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Odmietnuť"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> je dialóg hlasitosti"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Klepnutím obnovíte originál."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index a7eca4d4f435..2c8e22147ede 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Zavrni"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> je pogovorno okno glede prostornine"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Dotaknite se, če želite obnoviti izvirnik."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index edb116697596..a51f8ad226ae 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -400,7 +400,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Одбиј"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> је дијалог за јачину звука"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Додирните да бисте вратили оригинал."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index ee03be451e2b..ccf94ebdde69 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Neka"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> används som volymkontroll"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Tryck här om du vill återställa den ursprungliga appen."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 2ac3926b5bbc..c01d0acda444 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Kataa"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ni mazungumzo ya sauti"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Gusa ili urejeshe ya awali."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ta-rIN/strings.xml b/packages/SystemUI/res/values-ta-rIN/strings.xml
index a8177defb308..2632a819cdbb 100644
--- a/packages/SystemUI/res/values-ta-rIN/strings.xml
+++ b/packages/SystemUI/res/values-ta-rIN/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"நிராகரி"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"ஒலியளவு செய்தி: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"அசலை மீட்டமைக்கத் தொடவும்."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-te-rIN/strings.xml b/packages/SystemUI/res/values-te-rIN/strings.xml
index 009eddbb923e..8fe687eaedc7 100644
--- a/packages/SystemUI/res/values-te-rIN/strings.xml
+++ b/packages/SystemUI/res/values-te-rIN/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"తిరస్కరించు"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> అనేది వాల్యూమ్ డైలాగ్"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"అసలుదాన్ని పునరుద్ధరించడానికి తాకండి."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index bd0f58ef48ad..48b0a38ec158 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"ปฏิเสธ"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> เป็นช่องโต้ตอบระดับเสียง"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"แตะเพื่อคืนค่าดั้งเดิม"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index b8858af7bcd2..9c5daa8f678f 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Tanggihan"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ang volume dialog"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Pindutin upang ibalik ang orihinal."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 1b0d11f562b5..7c44bf1bcb7a 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Reddet"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ses denetimi iletişim kutusu olarak ayarlandı"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Orijinali geri yüklemek için dokunun."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index c5ebd3a0f91e..76ed71678ade 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Відхилити"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> призначено регулятором гучності"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Торкніться, щоб відновити оригінал."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-ur-rPK/strings.xml b/packages/SystemUI/res/values-ur-rPK/strings.xml
index c01a1978b1af..608c6d3184fa 100644
--- a/packages/SystemUI/res/values-ur-rPK/strings.xml
+++ b/packages/SystemUI/res/values-ur-rPK/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"مسترد کریں"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> والیوم ڈائلاگ ہے"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"اصل کو بحال کرنے کیلئے ٹچ کریں۔"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"‎@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-uz-rUZ/strings.xml b/packages/SystemUI/res/values-uz-rUZ/strings.xml
index 37e9542d39eb..63af257bf11f 100644
--- a/packages/SystemUI/res/values-uz-rUZ/strings.xml
+++ b/packages/SystemUI/res/values-uz-rUZ/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Rad etish"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> ovoz balandligini boshqaradi"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Aslini tiklash uchun bosing."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 3c2d0ef576c9..a4341686bc2b 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Từ chối"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"<xliff:g id="APP_NAME">%1$s</xliff:g> là hộp thoại khối lượng"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Chạm để khôi phục bản gốc."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 32e773572fe1..4e3c26554a55 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"拒绝"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”已用作音量控制对话框"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"触摸即可恢复原始设置。"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index bf8dfb23c46c..d6be63f75b08 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"拒絕"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」為音量對話框"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"輕觸即可復原。"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index f9b33ca8a03c..ddab028b69a1 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -401,7 +401,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"拒絕"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」現在是預設的音量控制對話方塊。"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"輕觸這裡即可恢復原始設定。"</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 9a32c4b77925..e91577d243b1 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -399,7 +399,6 @@
<string name="volumeui_prompt_deny" msgid="5720663643411696731">"Phika"</string>
<string name="volumeui_notification_title" msgid="4906770126345910955">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> yingxoxo yevolumu"</string>
<string name="volumeui_notification_text" msgid="1826889705095768656">"Thinta ukuze ubuyisele kokwangempela."</string>
- <string name="volume_zen_switch_text" msgid="8149183012610587643">"@*android:string/zen_mode_feature_name"</string>
<!-- no translation found for managed_profile_foreground_toast (3199278359979281097) -->
<skip />
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 051d2330ea87..88bf58a3ae1f 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -293,5 +293,8 @@
<!-- Enable the default volume dialog -->
<bool name="enable_volume_ui">true</bool>
+
+ <!-- Duration of the full carrier network change icon animation. -->
+ <integer name="carrier_network_change_anim_time">3000</integer>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ae134c6c045b..67a0bc61c4a8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -359,6 +359,9 @@
<!-- Content description of the airplane mode icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_airplane_mode">Airplane mode.</string>
+ <!-- Content description of the carrier network changing icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_carrier_network_change_mode">Carrier network changing.</string>
+
<!-- Content description of the battery level icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
@@ -998,7 +1001,7 @@
<string name="volumeui_notification_text">Touch to restore the original.</string>
<!-- Volume dialog zen toggle switch title -->
- <string name="volume_zen_switch_text">@*android:string/zen_mode_feature_name</string>
+ <string name="volume_zen_switch_text" translatable="false">@*android:string/zen_mode_feature_name</string>
<!-- Toast shown when user unlocks screen and managed profile activity is in the foreground -->
<string name="managed_profile_foreground_toast">You are in the Work profile</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 1790a4ec7375..5a84db547e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -18,6 +18,7 @@ package com.android.systemui.qs;
import android.content.Context;
import android.content.Intent;
+import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -325,7 +326,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
public static class ResourceIcon extends Icon {
private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
- private final int mResId;
+ protected final int mResId;
private ResourceIcon(int resId) {
mResId = resId;
@@ -342,7 +343,11 @@ public abstract class QSTile<TState extends State> implements Listenable {
@Override
public Drawable getDrawable(Context context) {
- return context.getDrawable(mResId);
+ Drawable d = context.getDrawable(mResId);
+ if (d instanceof Animatable) {
+ ((Animatable) d).start();
+ }
+ return d;
}
@Override
@@ -370,7 +375,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
@Override
public Drawable getDrawable(Context context) {
// workaround: get a clean state for every new AVD
- final AnimatedVectorDrawable d = (AnimatedVectorDrawable) super.getDrawable(context)
+ final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId)
.getConstantState().newDrawable();
d.start();
if (mAllowAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index ec83ca799880..af9d3a5919a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -38,6 +38,7 @@ import android.widget.TextView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.qs.QSTile.AnimationIcon;
import com.android.systemui.qs.QSTile.State;
import java.util.Objects;
@@ -315,8 +316,9 @@ public class QSTileView extends ViewGroup {
iv.setImageDrawable(d);
iv.setTag(R.id.qs_icon_tag, state.icon);
if (d instanceof Animatable) {
- if (!iv.isShown()) {
- ((Animatable) d).stop(); // skip directly to end state
+ Animatable a = (Animatable) d;
+ if (state.icon instanceof AnimationIcon && !iv.isShown()) {
+ a.stop(); // skip directly to end state
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index a82afcf5cc06..b2bb021be323 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -20,6 +20,8 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.PorterDuff;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
import android.telephony.SubscriptionInfo;
import android.util.AttributeSet;
import android.util.Log;
@@ -165,12 +167,13 @@ public class SignalClusterView
}
@Override
- public void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
- String contentDescription, String typeContentDescription, boolean isTypeIconWide,
- int subId) {
+ public void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon,
+ int typeIcon, String contentDescription, String typeContentDescription,
+ boolean isTypeIconWide, int subId) {
PhoneState state = getOrInflateState(subId);
state.mMobileVisible = visible;
state.mMobileStrengthId = strengthIcon;
+ state.mMobileDarkStrengthId = darkStrengthIcon;
state.mMobileTypeId = typeIcon;
state.mMobileDescription = contentDescription;
state.mMobileTypeDescription = typeContentDescription;
@@ -360,7 +363,7 @@ public class SignalClusterView
private class PhoneState {
private final int mSubId;
private boolean mMobileVisible = false;
- private int mMobileStrengthId = 0, mMobileTypeId = 0;
+ private int mMobileStrengthId = 0, mMobileDarkStrengthId = 0, mMobileTypeId = 0;
private boolean mIsMobileTypeIconWide;
private String mMobileDescription, mMobileTypeDescription;
@@ -384,7 +387,23 @@ public class SignalClusterView
public boolean apply(boolean isSecondaryIcon) {
if (mMobileVisible && !mIsAirplaneMode) {
mMobile.setImageResource(mMobileStrengthId);
+ Drawable mobileDrawable = mMobile.getDrawable();
+ if (mobileDrawable instanceof Animatable) {
+ Animatable ad = (Animatable) mobileDrawable;
+ if (!ad.isRunning()) {
+ ad.start();
+ }
+ }
+
mMobileDark.setImageResource(mMobileStrengthId);
+ Drawable mobileDarkDrawable = mMobileDark.getDrawable();
+ if (mobileDarkDrawable instanceof Animatable) {
+ Animatable ad = (Animatable) mobileDarkDrawable;
+ if (!ad.isRunning()) {
+ ad.start();
+ }
+ }
+
mMobileType.setImageResource(mMobileTypeId);
mMobileGroup.setContentDescription(mMobileTypeDescription
+ " " + mMobileDescription);
@@ -401,8 +420,9 @@ public class SignalClusterView
mMobileDark.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0,
0, 0, 0);
- if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
- (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
+ if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d dark=%d typ=%d",
+ (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId,
+ mMobileDarkStrengthId, mMobileTypeId));
mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index ba938cc29d36..c3c6b12315f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -114,6 +114,11 @@ public class MobileSignalController extends SignalController<
setInetCondition(inetCondition);
}
+ public void setCarrierNetworkChangeMode(boolean carrierNetworkChangeMode) {
+ mCurrentState.carrierNetworkChangeMode = carrierNetworkChangeMode;
+ notifyListenersIfNecessary();
+ }
+
/**
* Start listening for phone state changes.
*/
@@ -123,7 +128,8 @@ public class MobileSignalController extends SignalController<
| PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
| PhoneStateListener.LISTEN_CALL_STATE
| PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
- | PhoneStateListener.LISTEN_DATA_ACTIVITY);
+ | PhoneStateListener.LISTEN_DATA_ACTIVITY
+ | PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE);
}
/**
@@ -201,8 +207,12 @@ public class MobileSignalController extends SignalController<
&& !mCurrentState.isEmergency,
getQsCurrentIconId(), contentDescription,
qsTypeIcon,
- mCurrentState.dataConnected && mCurrentState.activityIn,
- mCurrentState.dataConnected && mCurrentState.activityOut,
+ mCurrentState.dataConnected
+ && !mCurrentState.carrierNetworkChangeMode
+ && mCurrentState.activityIn,
+ mCurrentState.dataConnected
+ && !mCurrentState.carrierNetworkChangeMode
+ && mCurrentState.activityOut,
dataContentDescription,
mCurrentState.isEmergency ? null : mCurrentState.networkName,
// Only wide if actually showing something.
@@ -215,6 +225,7 @@ public class MobileSignalController extends SignalController<
mSignalClusters.get(i).setMobileDataIndicators(
mCurrentState.enabled && !mCurrentState.airplaneMode,
getCurrentIconId(),
+ getCurrentDarkIconId(),
typeIcon,
contentDescription,
dataContentDescription,
@@ -224,6 +235,10 @@ public class MobileSignalController extends SignalController<
}
}
+ private int getCurrentDarkIconId() {
+ return getCurrentIconId(false /* light */);
+ }
+
@Override
protected MobileState cleanState() {
return new MobileState();
@@ -270,6 +285,10 @@ public class MobileSignalController extends SignalController<
}
}
+ private boolean isCarrierNetworkChangeActive() {
+ return !hasService() && mCurrentState.carrierNetworkChangeMode;
+ }
+
public void handleBroadcast(Intent intent) {
String action = intent.getAction();
if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
@@ -351,7 +370,9 @@ public class MobileSignalController extends SignalController<
mCurrentState.dataConnected = mCurrentState.connected
&& mDataState == TelephonyManager.DATA_CONNECTED;
- if (isRoaming()) {
+ if (isCarrierNetworkChangeActive()) {
+ mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
+ } else if (isRoaming()) {
mCurrentState.iconGroup = TelephonyIcons.ROAMING;
}
if (isEmergencyOnly() != mCurrentState.isEmergency) {
@@ -363,6 +384,7 @@ public class MobileSignalController extends SignalController<
&& mServiceState.getOperatorAlphaShort() != null) {
mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
}
+
notifyListenersIfNecessary();
}
@@ -428,6 +450,16 @@ public class MobileSignalController extends SignalController<
}
setActivity(direction);
}
+
+ @Override
+ public void onCarrierNetworkChange(boolean active) {
+ if (DEBUG) {
+ Log.d(mTag, "onCarrierNetworkChange: active=" + active);
+ }
+ mCurrentState.carrierNetworkChangeMode = active;
+
+ updateTelephony();
+ }
};
static class MobileIconGroup extends SignalController.IconGroup {
@@ -440,8 +472,17 @@ public class MobileSignalController extends SignalController<
int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
int[] qsDataType) {
- super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState,
- qsDiscState, discContentDesc);
+ this(name, sbIcons, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState,
+ sbDiscState, sbDiscState, qsDiscState, discContentDesc, dataContentDesc,
+ dataType, isWide, qsDataType);
+ }
+
+ public MobileIconGroup(String name, int[][] sbIcons, int[][] sbDarkIcons, int[][] qsIcons,
+ int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState,
+ int sbDarkDiscState, int qsDiscState, int discContentDesc, int dataContentDesc,
+ int dataType, boolean isWide, int[] qsDataType) {
+ super(name, sbIcons, sbDarkIcons, qsIcons, contentDesc, sbNullState, qsNullState,
+ sbDiscState, sbDarkDiscState, qsDiscState, discContentDesc);
mDataContentDescription = dataContentDesc;
mDataType = dataType;
mIsWide = isWide;
@@ -455,6 +496,7 @@ public class MobileSignalController extends SignalController<
boolean dataConnected;
boolean isEmergency;
boolean airplaneMode;
+ boolean carrierNetworkChangeMode;
int inetForNetwork;
@Override
@@ -467,6 +509,7 @@ public class MobileSignalController extends SignalController<
inetForNetwork = state.inetForNetwork;
isEmergency = state.isEmergency;
airplaneMode = state.airplaneMode;
+ carrierNetworkChangeMode = state.carrierNetworkChangeMode;
}
@Override
@@ -478,7 +521,8 @@ public class MobileSignalController extends SignalController<
builder.append("dataConnected=").append(dataConnected).append(',');
builder.append("inetForNetwork=").append(inetForNetwork).append(',');
builder.append("isEmergency=").append(isEmergency).append(',');
- builder.append("airplaneMode=").append(airplaneMode);
+ builder.append("airplaneMode=").append(airplaneMode).append(',');
+ builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode);
}
@Override
@@ -489,6 +533,7 @@ public class MobileSignalController extends SignalController<
&& ((MobileState) o).dataConnected == dataConnected
&& ((MobileState) o).isEmergency == isEmergency
&& ((MobileState) o).airplaneMode == airplaneMode
+ && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
&& ((MobileState) o).inetForNetwork == inetForNetwork;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index bb3eb7ad5ab8..5cf6a6ea157b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -704,6 +704,13 @@ public class NetworkControllerImpl extends BroadcastReceiver
controller.getState().enabled = show;
controller.notifyListeners();
}
+ String carrierNetworkChange = args.getString("carriernetworkchange");
+ if (carrierNetworkChange != null) {
+ boolean show = carrierNetworkChange.equals("show");
+ for (MobileSignalController controller : mMobileSignalControllers.values()) {
+ controller.setCarrierNetworkChangeMode(show);
+ }
+ }
}
}
@@ -718,9 +725,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
public interface SignalCluster {
void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
- void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
- String contentDescription, String typeContentDescription, boolean isTypeIconWide,
- int subId);
+ void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon,
+ int typeIcon, String contentDescription, String typeContentDescription,
+ boolean isTypeIconWide, int subId);
void setSubs(List<SubscriptionInfo> subs);
void setNoSims(boolean show);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 1d96c6b39499..c204814c2ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -142,8 +142,16 @@ public abstract class SignalController<T extends SignalController.State,
* Gets the signal icon for SB based on current state of connected, enabled, and level.
*/
public int getCurrentIconId() {
+ return getCurrentIconId(true /* light */);
+ }
+
+ protected int getCurrentIconId(boolean light) {
if (mCurrentState.connected) {
- return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
+ if (light) {
+ return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
+ } else {
+ return getIcons().mSbDarkIcons[mCurrentState.inetCondition][mCurrentState.level];
+ }
} else if (mCurrentState.enabled) {
return getIcons().mSbDiscState;
} else {
@@ -226,11 +234,13 @@ public abstract class SignalController<T extends SignalController.State,
*/
static class IconGroup {
final int[][] mSbIcons;
+ final int[][] mSbDarkIcons;
final int[][] mQsIcons;
final int[] mContentDesc;
final int mSbNullState;
final int mQsNullState;
final int mSbDiscState;
+ final int mSbDarkDiscState;
final int mQsDiscState;
final int mDiscContentDesc;
// For logging.
@@ -239,13 +249,22 @@ public abstract class SignalController<T extends SignalController.State,
public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
int discContentDesc) {
+ this(name, sbIcons, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState,
+ sbDiscState, sbDiscState, qsDiscState, discContentDesc);
+ }
+
+ public IconGroup(String name, int[][] sbIcons, int[][] sbDarkIcons, int[][] qsIcons,
+ int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState,
+ int sbDarkDiscState, int qsDiscState, int discContentDesc) {
mName = name;
mSbIcons = sbIcons;
+ mSbDarkIcons = sbDarkIcons;
mQsIcons = qsIcons;
mContentDesc = contentDesc;
mSbNullState = sbNullState;
mQsNullState = qsNullState;
mSbDiscState = sbDiscState;
+ mSbDarkDiscState = sbDarkDiscState;
mQsDiscState = qsDiscState;
mDiscContentDesc = discContentDesc;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index d266ed88b5e5..053feb12edff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -68,6 +68,42 @@ class TelephonyIcons {
R.drawable.stat_sys_signal_4_fully }
};
+ //CarrierNetworkChange
+ static final int[][] TELEPHONY_CARRIER_NETWORK_CHANGE = {
+ { R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation },
+ { R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_carrier_network_change_animation }
+ };
+
+ static final int[][] TELEPHONY_CARRIER_NETWORK_CHANGE_DARK = {
+ { R.drawable.stat_sys_signal_dark_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_dark_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_dark_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_dark_carrier_network_change_animation },
+ { R.drawable.stat_sys_signal_dark_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_dark_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_dark_carrier_network_change_animation,
+ R.drawable.stat_sys_signal_dark_carrier_network_change_animation }
+ };
+
+ static final int[][] QS_TELEPHONY_CARRIER_NETWORK_CHANGE = {
+ { R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation },
+ { R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation,
+ R.drawable.ic_qs_signal_carrier_network_change_animation }
+ };
+
static final int[] QS_DATA_R = {
R.drawable.ic_qs_signal_r,
R.drawable.ic_qs_signal_r
@@ -202,11 +238,34 @@ class TelephonyIcons {
static final int ICON_3G = R.drawable.stat_sys_data_fully_connected_3g;
static final int ICON_4G = R.drawable.stat_sys_data_fully_connected_4g;
static final int ICON_1X = R.drawable.stat_sys_data_fully_connected_1x;
+ static final int ICON_CARRIER_NETWORK_CHANGE =
+ R.drawable.stat_sys_signal_carrier_network_change_animation;
+ static final int ICON_CARRIER_NETWORK_CHANGE_DARK =
+ R.drawable.stat_sys_signal_dark_carrier_network_change_animation;
static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte;
static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g;
static final int QS_ICON_4G = R.drawable.ic_qs_signal_4g;
static final int QS_ICON_1X = R.drawable.ic_qs_signal_1x;
+ static final int QS_ICON_CARRIER_NETWORK_CHANGE =
+ R.drawable.ic_qs_signal_carrier_network_change_animation;
+
+ static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
+ "CARRIER_NETWORK_CHANGE",
+ TelephonyIcons.TELEPHONY_CARRIER_NETWORK_CHANGE,
+ TelephonyIcons.TELEPHONY_CARRIER_NETWORK_CHANGE_DARK,
+ TelephonyIcons.QS_TELEPHONY_CARRIER_NETWORK_CHANGE,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.ICON_CARRIER_NETWORK_CHANGE,
+ TelephonyIcons.ICON_CARRIER_NETWORK_CHANGE_DARK,
+ TelephonyIcons.QS_ICON_CARRIER_NETWORK_CHANGE,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_carrier_network_change_mode,
+ 0,
+ false,
+ null
+ );
static final MobileIconGroup THREE_G = new MobileIconGroup(
"3G",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 5d884076a1f3..5d40eed3b9ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -279,7 +279,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
// TODO: Verify all fields.
Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setMobileDataIndicators(
- visibleArg.capture(), iconArg.capture(), typeIconArg.capture(),
+ visibleArg.capture(), iconArg.capture(), iconArg.capture(), typeIconArg.capture(),
ArgumentCaptor.forClass(String.class).capture(),
ArgumentCaptor.forClass(String.class).capture(),
ArgumentCaptor.forClass(Boolean.class).capture(),
diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java
index 2203850bd03c..3b61f9dfe798 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -276,7 +276,7 @@ public class Allocation extends BaseObj {
* Enable/Disable AutoPadding for Vec3 elements.
* By default: Diabled.
*
- * @param useAutoPadding True: enable AutoPadding; flase: disable AutoPadding
+ * @param useAutoPadding True: enable AutoPadding; False: disable AutoPadding
*
*/
public void setAutoPadding(boolean useAutoPadding) {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index ef51ad62c11e..3e5eee86a777 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -112,6 +112,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private static final int SERVICE_IBLUETOOTHGATT = 2;
private final Context mContext;
+ private static int mBleAppCount = 0;
// Locks are not provided for mName and mAddress.
// They are accessed in handler or broadcast receiver, same thread context.
@@ -184,11 +185,40 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
}
}
+
+ int st = BluetoothAdapter.STATE_OFF;
+ if (mBluetooth != null) {
+ try {
+ st = mBluetooth.getState();
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to call getState", e);
+ }
+ }
+ Log.d(TAG, "state" + st);
+
if (isAirplaneModeOn()) {
- // disable without persisting the setting
- sendDisableMsg();
+ // Clear registered LE apps to force shut-off
+ synchronized (this) {
+ mBleAppCount = 0;
+ }
+ if (st == BluetoothAdapter.STATE_BLE_ON) {
+ //if state is BLE_ON make sure you trigger disableBLE part
+ try {
+ if (mBluetooth != null) {
+ mBluetooth.onBrEdrDown();
+ mEnableExternal = false;
+ }
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to call onBrEdrDown", e);
+ }
+ } else if (st == BluetoothAdapter.STATE_ON){
+ // disable without persisting the setting
+ Log.d(TAG, "Calling disable");
+ sendDisableMsg();
+ }
} else if (mEnableExternal) {
// enable without persisting the setting
+ Log.d(TAG, "Calling enable");
sendEnableMsg(mQuietEnableExternal);
}
}
@@ -203,12 +233,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
sendEnableMsg(mQuietEnableExternal);
}
}
-
- if (!isNameAndAddressSet()) {
- //Sync the Bluetooth name and address from the Bluetooth Adapter
- if (DBG) Log.d(TAG,"Retrieving Bluetooth Adapter name and address...");
- getNameAndAddress();
- }
}
}
};
@@ -218,6 +242,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
mContext = context;
mBluetooth = null;
+ mBluetoothGatt = null;
mBinding = false;
mUnbinding = false;
mEnable = false;
@@ -396,6 +421,133 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
return false;
}
+ class ClientDeathRecipient implements IBinder.DeathRecipient {
+ public void binderDied() {
+ if (DBG) Log.d(TAG, "Binder is dead - unregister Ble App");
+ if (mBleAppCount > 0) --mBleAppCount;
+
+ if (mBleAppCount == 0) {
+ if (DBG) Log.d(TAG, "Disabling LE only mode after application crash");
+ try {
+ if (mBluetooth != null) {
+ mBluetooth.onBrEdrDown();
+ }
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to call onBrEdrDown", e);
+ }
+ }
+ }
+ }
+
+ /** Internal death rec list */
+ Map<IBinder, ClientDeathRecipient> mBleApps = new HashMap<IBinder, ClientDeathRecipient>();
+
+ public int updateBleAppCount(IBinder token, boolean enable) {
+ if (enable) {
+ ClientDeathRecipient r = mBleApps.get(token);
+ if (r == null) {
+ ClientDeathRecipient deathRec = new ClientDeathRecipient();
+ try {
+ token.linkToDeath(deathRec, 0);
+ } catch (RemoteException ex) {
+ throw new IllegalArgumentException("Wake lock is already dead.");
+ }
+ mBleApps.put(token, deathRec);
+ synchronized (this) {
+ ++mBleAppCount;
+ }
+ if (DBG) Log.d(TAG, "Registered for death Notification");
+ }
+
+ } else {
+ ClientDeathRecipient r = mBleApps.get(token);
+ if (r != null) {
+ try {
+ token.linkToDeath(r, 0);
+ } catch (RemoteException ex) {
+ throw new IllegalArgumentException("Wake lock is already dead.");
+ }
+ mBleApps.remove(token);
+ synchronized (this) {
+ if (mBleAppCount > 0) --mBleAppCount;
+ }
+ if (DBG) Log.d(TAG, "Unregistered for death Notification");
+ }
+ }
+ if (DBG) Log.d(TAG, "Updated BleAppCount" + mBleAppCount);
+ if (mBleAppCount == 0 && mEnable) {
+ try {
+ if (mBluetooth != null && (mBluetooth.getState() != BluetoothAdapter.STATE_ON)) {
+ if (DBG) Log.d(TAG, "Reseting the mEnable flag for clean disable");
+ mEnable = false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getState()", e);
+ }
+ }
+ return mBleAppCount;
+ }
+
+ /** @hide*/
+ public boolean isBleAppPresent() {
+ if (DBG) Log.d(TAG, "isBleAppPresent() count: " + mBleAppCount);
+ return (mBleAppCount > 0);
+ }
+
+ /**
+ * Action taken when GattService is turned off
+ */
+ private void onBluetoothGattServiceUp() {
+ if (DBG) Log.d(TAG,"BluetoothGatt Service is Up");
+ try{
+ if (isBleAppPresent() == false && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+ mBluetooth.onLeServiceUp();
+
+ // waive WRITE_SECURE_SETTINGS permission check
+ long callingIdentity = Binder.clearCallingIdentity();
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to call onServiceUp", e);
+ }
+ }
+
+ /**
+ * Inform BluetoothAdapter instances that BREDR part is down
+ * and turn off all service and stack if no LE app needs it
+ */
+ private void sendBrEdrDownCallback() {
+ if (DBG) Log.d(TAG,"Calling sendBrEdrDownCallback callbacks");
+ int n = mCallbacks.beginBroadcast();
+
+ if (isBleAppPresent() == false) {
+ try {
+ mBluetooth.onBrEdrDown();
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to call onBrEdrDown", e);
+ }
+ }
+ else{//need to stay at BLE ON. disconnect all Gatt connections
+ try{
+ mBluetoothGatt.unregAll();//disconnectAll();
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to disconn all", e);
+ }
+ }
+
+ Log.d(TAG,"Broadcasting onBrEdrDown() to " + n + " receivers.");
+ for (int i=0; i <n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBrEdrDown();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to call sendBrEdrDownCallback() on callback #" + i, e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ /** @hide*/
public void getNameAndAddress() {
if (DBG) {
Log.d(TAG,"getNameAndAddress(): mBluetooth = " + mBluetooth +
@@ -445,11 +597,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
mQuietEnableExternal = false;
mEnableExternal = true;
// waive WRITE_SECURE_SETTINGS permission check
- long callingIdentity = Binder.clearCallingIdentity();
- persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
- Binder.restoreCallingIdentity(callingIdentity);
sendEnableMsg(false);
}
+ if (DBG) Log.d(TAG, "enable returning");
return true;
}
@@ -508,6 +658,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
} else {
mUnbinding=false;
}
+ mBluetoothGatt = null;
}
}
@@ -1034,6 +1185,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
synchronized(mConnection) {
if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
mBluetoothGatt = IBluetoothGatt.Stub.asInterface(service);
+ onBluetoothGattServiceUp();
break;
} // else must be SERVICE_IBLUETOOTH
@@ -1111,12 +1263,18 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
bluetoothStateChangeHandler(prevState, newState);
// handle error state transition case from TURNING_ON to OFF
// unbind and rebind bluetooth service and enable bluetooth
- if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
+ if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_ON) &&
(newState == BluetoothAdapter.STATE_OFF) &&
(mBluetooth != null) && mEnable) {
recoverBluetoothServiceFromError();
}
- if (newState == BluetoothAdapter.STATE_ON) {
+ if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
+ (newState == BluetoothAdapter.STATE_BLE_ON) &&
+ (mBluetooth != null) && mEnable) {
+ recoverBluetoothServiceFromError();
+ }
+ if (newState == BluetoothAdapter.STATE_ON ||
+ newState == BluetoothAdapter.STATE_BLE_ON) {
// bluetooth is working, reset the counter
if (mErrorRecoveryRetryCounter != 0) {
Log.w(TAG, "bluetooth is recovered from error");
@@ -1376,39 +1534,90 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
return valid;
}
+ private void sendBleStateChanged(int prevState, int newState) {
+ if (DBG) Log.d(TAG,"BLE State Change Intent: " + prevState + " -> " + newState);
+ // Send broadcast message to everyone else
+ Intent intent = new Intent(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
+ intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM);
+ }
+
private void bluetoothStateChangeHandler(int prevState, int newState) {
+ boolean isStandardBroadcast = true;
if (prevState != newState) {
//Notify all proxy objects first of adapter state change
- if (newState == BluetoothAdapter.STATE_ON || newState == BluetoothAdapter.STATE_OFF) {
- boolean isUp = (newState==BluetoothAdapter.STATE_ON);
- sendBluetoothStateCallback(isUp);
-
- if (isUp) {
- // connect to GattService
- if (mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_BLUETOOTH_LE)) {
- Intent i = new Intent(IBluetoothGatt.class.getName());
- doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
- UserHandle.CURRENT);
- }
- } else {
- //If Bluetooth is off, send service down event to proxy objects, and unbind
- if (!isUp && canUnbindBluetoothService()) {
- unbindAllBluetoothProfileServices();
+ if (newState == BluetoothAdapter.STATE_BLE_ON
+ || newState == BluetoothAdapter.STATE_OFF) {
+ boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF
+ && newState == BluetoothAdapter.STATE_BLE_ON);
+
+ if (newState == BluetoothAdapter.STATE_OFF) {
+ // If Bluetooth is off, send service down event to proxy objects, and unbind
+ if (DBG) Log.d(TAG, "Bluetooth is complete turn off");
+ if (canUnbindBluetoothService()) {
+ if (DBG) Log.d(TAG, "Good to unbind!");
sendBluetoothServiceDownCallback();
unbindAndFinish();
+ sendBleStateChanged(prevState, newState);
+ // Don't broadcast as it has already been broadcast before
+ isStandardBroadcast = false;
}
+
+ } else if (!intermediate_off) {
+ // connect to GattService
+ if (DBG) Log.d(TAG, "Bluetooth is in LE only mode");
+ if (mBluetoothGatt != null) {
+ if (DBG) Log.d(TAG, "Calling BluetoothGattServiceUp");
+ onBluetoothGattServiceUp();
+ } else {
+ if (DBG) Log.d(TAG, "Binding Bluetooth GATT service");
+ if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Intent i = new Intent(IBluetoothGatt.class.getName());
+ doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT);
+ }
+ }
+ sendBleStateChanged(prevState, newState);
+ //Don't broadcase this as std intent
+ isStandardBroadcast = false;
+
+ } else if (intermediate_off){
+ if (DBG) Log.d(TAG, "Intermediate off, back to LE only mode");
+ // For LE only mode, broadcast as is
+ sendBleStateChanged(prevState, newState);
+ sendBluetoothStateCallback(false); // BT is OFF for general users
+ // Broadcast as STATE_OFF
+ newState = BluetoothAdapter.STATE_OFF;
+ sendBrEdrDownCallback();
}
+ } else if (newState == BluetoothAdapter.STATE_ON) {
+ boolean isUp = (newState==BluetoothAdapter.STATE_ON);
+ sendBluetoothStateCallback(isUp);
+ sendBleStateChanged(prevState, newState);
+
+ } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON
+ || newState == BluetoothAdapter.STATE_BLE_TURNING_OFF ) {
+ sendBleStateChanged(prevState, newState);
+ isStandardBroadcast = false;
+
+ } else if (newState == BluetoothAdapter.STATE_TURNING_ON
+ || newState == BluetoothAdapter.STATE_TURNING_OFF) {
+ sendBleStateChanged(prevState, newState);
}
- //Send broadcast message to everyone else
- Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
- intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- if (DBG) Log.d(TAG,"Bluetooth State Change Intent: " + prevState + " -> " + newState);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- BLUETOOTH_PERM);
+ if (isStandardBroadcast) {
+ if (prevState == BluetoothAdapter.STATE_BLE_ON) {
+ // Show prevState of BLE_ON as OFF to standard users
+ prevState = BluetoothAdapter.STATE_OFF;
+ }
+ Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+ intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM);
+ }
}
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b5796c9e3192..b785d3dc0ce4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -19,6 +19,7 @@ package com.android.server;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -89,6 +90,7 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.Xml;
@@ -709,16 +711,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
return mNextNetworkRequestId++;
}
- private void assignNextNetId(NetworkAgentInfo nai) {
+ private int reserveNetId() {
synchronized (mNetworkForNetId) {
for (int i = MIN_NET_ID; i <= MAX_NET_ID; i++) {
int netId = mNextNetId;
if (++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID;
// Make sure NetID unused. http://b/16815182
- if (mNetworkForNetId.get(netId) == null) {
- nai.network = new Network(netId);
- mNetworkForNetId.put(netId, nai);
- return;
+ if (!mNetIdInUse.get(netId)) {
+ mNetIdInUse.put(netId, true);
+ return netId;
}
}
}
@@ -739,7 +740,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
info = new NetworkInfo(nai.networkInfo);
lp = new LinkProperties(nai.linkProperties);
nc = new NetworkCapabilities(nai.networkCapabilities);
- network = new Network(nai.network);
+ // Network objects are outwardly immutable so there is no point to duplicating.
+ // Duplicating also precludes sharing socket factories and connection pools.
+ network = nai.network;
subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null;
}
info.setType(networkType);
@@ -807,7 +810,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
info = new NetworkInfo(nai.networkInfo);
lp = new LinkProperties(nai.linkProperties);
nc = new NetworkCapabilities(nai.networkCapabilities);
- network = new Network(nai.network);
+ // Network objects are outwardly immutable so there is no point to duplicating.
+ // Duplicating also precludes sharing socket factories and connection pools.
+ network = nai.network;
subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null;
}
}
@@ -873,6 +878,28 @@ public class ConnectivityService extends IConnectivityManager.Stub
return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
}
+ @Override
+ public Network getActiveNetwork() {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ final int user = UserHandle.getUserId(uid);
+ int vpnNetId = NETID_UNSET;
+ synchronized (mVpns) {
+ final Vpn vpn = mVpns.get(user);
+ if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId();
+ }
+ NetworkAgentInfo nai;
+ if (vpnNetId != NETID_UNSET) {
+ synchronized (mNetworkForNetId) {
+ nai = mNetworkForNetId.get(vpnNetId);
+ }
+ if (nai != null) return nai.network;
+ }
+ nai = getDefaultNetwork();
+ if (nai != null && isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid)) nai = null;
+ return nai != null ? nai.network : null;
+ }
+
/**
* Find the first Provisioning network.
*
@@ -985,13 +1012,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public Network[] getAllNetworks() {
enforceAccessPermission();
- final ArrayList<Network> result = new ArrayList();
synchronized (mNetworkForNetId) {
+ final Network[] result = new Network[mNetworkForNetId.size()];
for (int i = 0; i < mNetworkForNetId.size(); i++) {
- result.add(new Network(mNetworkForNetId.valueAt(i).network));
+ result[i] = mNetworkForNetId.valueAt(i).network;
}
+ return result;
}
- return result.toArray(new Network[result.size()]);
}
private NetworkCapabilities getNetworkCapabilitiesAndValidation(NetworkAgentInfo nai) {
@@ -1960,6 +1987,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (nai != null) {
synchronized (mNetworkForNetId) {
mNetworkForNetId.remove(nai.network.netId);
+ mNetIdInUse.delete(nai.network.netId);
}
// Just in case.
mLegacyTypeTracker.remove(nai);
@@ -2003,6 +2031,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mLegacyTypeTracker.remove(nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.remove(nai.network.netId);
+ mNetIdInUse.delete(nai.network.netId);
}
// Since we've lost the network, go through all the requests that
// it was satisfying and see if any other factory can satisfy them.
@@ -2549,25 +2578,27 @@ public class ConnectivityService extends IConnectivityManager.Stub
public void reportInetCondition(int networkType, int percentage) {
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
if (nai == null) return;
- boolean isGood = percentage > 50;
- // Revalidate if the app report does not match our current validated state.
- if (isGood != nai.lastValidated) {
- // Make the message logged by reportBadNetwork below less confusing.
- if (DBG && isGood) log("reportInetCondition: type=" + networkType + " ok, revalidate");
- reportBadNetwork(nai.network);
- }
+ reportNetworkConnectivity(nai.network, percentage > 50);
}
- public void reportBadNetwork(Network network) {
+ public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
enforceAccessPermission();
enforceInternetPermission();
- if (network == null) return;
-
- final int uid = Binder.getCallingUid();
- NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ NetworkAgentInfo nai;
+ if (network == null) {
+ nai = getDefaultNetwork();
+ } else {
+ nai = getNetworkAgentInfoForNetwork(network);
+ }
if (nai == null) return;
- if (DBG) log("reportBadNetwork(" + nai.name() + ") by " + uid);
+ // Revalidate if the app report does not match our current validated state.
+ if (hasConnectivity == nai.lastValidated) return;
+ final int uid = Binder.getCallingUid();
+ if (DBG) {
+ log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity +
+ ") by " + uid);
+ }
synchronized (nai) {
// Validating an uncreated network could result in a call to rematchNetworkAndRequests()
// which isn't meant to work on uncreated networks.
@@ -3026,23 +3057,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
- public int findConnectionTypeForIface(String iface) {
- enforceConnectivityInternalPermission();
-
- if (TextUtils.isEmpty(iface)) return ConnectivityManager.TYPE_NONE;
-
- synchronized(mNetworkForNetId) {
- for (int i = 0; i < mNetworkForNetId.size(); i++) {
- NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
- LinkProperties lp = nai.linkProperties;
- if (lp != null && iface.equals(lp.getInterfaceName()) && nai.networkInfo != null) {
- return nai.networkInfo.getType();
- }
- }
- }
- return ConnectivityManager.TYPE_NONE;
- }
-
@Override
public int checkMobileProvisioning(int suggestedTimeOutMs) {
// TODO: Remove? Any reason to trigger a provisioning check?
@@ -3296,7 +3310,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
loge("Starting user already has a VPN");
return;
}
- userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, this, userId);
+ userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
mVpns.put(userId, userVpn);
}
}
@@ -3441,6 +3455,24 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
+ @Override
+ public boolean requestBwUpdate(Network network) {
+ enforceAccessPermission();
+ NetworkAgentInfo nai = null;
+ if (network == null) {
+ return false;
+ }
+ synchronized (mNetworkForNetId) {
+ nai = mNetworkForNetId.get(network.netId);
+ }
+ if (nai != null) {
+ nai.asyncChannel.sendMessage(android.net.NetworkAgent.CMD_REQUEST_BANDWIDTH_UPDATE);
+ return true;
+ }
+ return false;
+ }
+
+
private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
// if UID is restricted, don't allow them to bring up metered APNs
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
@@ -3548,14 +3580,23 @@ public class ConnectivityService extends IConnectivityManager.Stub
* and the are the highest scored network available.
* the are keyed off the Requests requestId.
*/
+ // TODO: Yikes, this is accessed on multiple threads: add synchronization.
private final SparseArray<NetworkAgentInfo> mNetworkForRequestId =
new SparseArray<NetworkAgentInfo>();
+ // NOTE: Accessed on multiple threads, must be synchronized on itself.
+ @GuardedBy("mNetworkForNetId")
private final SparseArray<NetworkAgentInfo> mNetworkForNetId =
new SparseArray<NetworkAgentInfo>();
+ // NOTE: Accessed on multiple threads, synchronized with mNetworkForNetId.
+ // An entry is first added to mNetIdInUse, prior to mNetworkForNetId, so
+ // there may not be a strict 1:1 correlation between the two.
+ @GuardedBy("mNetworkForNetId")
+ private final SparseBooleanArray mNetIdInUse = new SparseBooleanArray();
// NetworkAgentInfo keyed off its connecting messenger
// TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays
+ // NOTE: Only should be accessed on ConnectivityServiceThread, except dump().
private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
new HashMap<Messenger, NetworkAgentInfo>();
@@ -3570,7 +3611,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
return nai == getDefaultNetwork();
}
- public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+ public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
int currentScore, NetworkMisc networkMisc) {
enforceConnectivityInternalPermission();
@@ -3578,20 +3619,23 @@ public class ConnectivityService extends IConnectivityManager.Stub
// TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network
// satisfies mDefaultRequest.
NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
- new NetworkInfo(networkInfo), new LinkProperties(linkProperties),
- new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler,
- new NetworkMisc(networkMisc), mDefaultRequest);
+ new Network(reserveNetId()), new NetworkInfo(networkInfo), new LinkProperties(
+ linkProperties), new NetworkCapabilities(networkCapabilities), currentScore,
+ mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest);
synchronized (this) {
nai.networkMonitor.systemReady = mSystemReady;
}
if (DBG) log("registerNetworkAgent " + nai);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
+ return nai.network.netId;
}
private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
if (VDBG) log("Got NetworkAgent Messenger");
mNetworkAgentInfos.put(na.messenger, na);
- assignNextNetId(na);
+ synchronized (mNetworkForNetId) {
+ mNetworkForNetId.put(na.network.netId, na);
+ }
na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
NetworkInfo networkInfo = na.networkInfo;
na.networkInfo = null;
@@ -4211,9 +4255,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
networkAgent.created = true;
updateLinkProperties(networkAgent, null);
notifyIfacesChanged();
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+
networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
scheduleUnvalidatedPrompt(networkAgent);
+
if (networkAgent.isVPN()) {
// Temporarily disable the default proxy (not global).
synchronized (mProxyLock) {
@@ -4226,9 +4271,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
// TODO: support proxy per network.
}
+
// Consider network even though it is not yet validated.
rematchNetworkAndRequests(networkAgent, NascentState.NOT_JUST_VALIDATED,
ReapUnvalidatedNetworks.REAP);
+
+ // This has to happen after matching the requests, because callbacks are just requests.
+ notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
} else if (state == NetworkInfo.State.DISCONNECTED ||
state == NetworkInfo.State.SUSPENDED) {
networkAgent.asyncChannel.disconnect();
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index d1532331ef32..908ee22c75ec 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -181,6 +181,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private PreciseCallState mPreciseCallState = new PreciseCallState();
+ private boolean mCarrierNetworkChangeState = false;
+
private PreciseDataConnectionState mPreciseDataConnectionState =
new PreciseDataConnectionState();
@@ -607,6 +609,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
remove(r.binder);
}
}
+ if ((events & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) {
+ try {
+ r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
} else {
@@ -790,6 +799,31 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
broadcastSignalStrengthChanged(signalStrength, subId);
}
+ @Override
+ public void notifyCarrierNetworkChange(boolean active) {
+ if (!checkNotifyPermissionOrCarrierPrivilege("notifyCarrierNetworkChange()")) {
+ return;
+ }
+ if (VDBG) {
+ log("notifyCarrierNetworkChange: active=" + active);
+ }
+
+ synchronized (mRecords) {
+ mCarrierNetworkChangeState = active;
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE)) {
+ try {
+ r.callback.onCarrierNetworkChange(active);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
public void notifyCellInfo(List<CellInfo> cellInfo) {
notifyCellInfoForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cellInfo);
}
@@ -1422,9 +1456,19 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
android.Manifest.permission.READ_PRECISE_PHONE_STATE);
}
+ private boolean checkNotifyPermissionOrCarrierPrivilege(String method) {
+ if (checkNotifyPermission() || checkCarrierPrivilege()) {
+ return true;
+ }
+
+ String msg = "Modify Phone State or Carrier Privilege Permission Denial: " + method
+ + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+ if (DBG) log(msg);
+ return false;
+ }
+
private boolean checkNotifyPermission(String method) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- == PackageManager.PERMISSION_GRANTED) {
+ if (checkNotifyPermission()) {
return true;
}
String msg = "Modify Phone State Permission Denial: " + method + " from pid="
@@ -1433,6 +1477,24 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
return false;
}
+ private boolean checkNotifyPermission() {
+ return mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean checkCarrierPrivilege() {
+ TelephonyManager tm = TelephonyManager.getDefault();
+ String[] pkgs = mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
+ for (String pkg : pkgs) {
+ if (tm.checkCarrierPrivilegesForPackage(pkg) ==
+ TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private void checkListenerPermission(int events) {
if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
mContext.enforceCallingOrSelfPermission(
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index dac0580af479..8a7c9020d952 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -40,7 +40,10 @@ import java.util.ArrayList;
*/
public class NetworkAgentInfo {
public NetworkInfo networkInfo;
- public Network network;
+ // This Network object should always be used if possible, so as to encourage reuse of the
+ // enclosed socket factory and connection pool. Avoid creating other Network objects.
+ // This Network object is always valid.
+ public final Network network;
public LinkProperties linkProperties;
public NetworkCapabilities networkCapabilities;
public final NetworkMonitor networkMonitor;
@@ -86,12 +89,12 @@ public class NetworkAgentInfo {
// Used by ConnectivityService to keep track of 464xlat.
public Nat464Xlat clatd;
- public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, NetworkInfo info,
+ public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
NetworkMisc misc, NetworkRequest defaultRequest) {
this.messenger = messenger;
asyncChannel = ac;
- network = null;
+ network = net;
networkInfo = info;
linkProperties = lp;
networkCapabilities = nc;
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index d0e1665e19ae..7e202768cffc 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -216,7 +216,7 @@ public class NetworkMonitor extends StateMachine {
// If a network is not validated, make one attempt every 10 mins to see if it starts working.
private static final int REEVALUATE_PAUSE_MS = 10*60*1000;
private static final int PERIODIC_ATTEMPTS = 1;
- // When an application calls reportBadNetwork, only make one attempt.
+ // When an application calls reportNetworkConnectivity, only make one attempt.
private static final int REEVALUATE_ATTEMPTS = 1;
private final int mReevaluateDelayMs;
private int mReevaluateToken = 0;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 0b430ea45624..3d478f9a2eec 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -17,8 +17,13 @@
package com.android.server.connectivity;
import static android.Manifest.permission.BIND_VPN_SERVICE;
+import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.os.UserHandle.PER_USER_RANGE;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
import android.Manifest;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -29,6 +34,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -64,6 +70,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.security.Credentials;
import android.security.KeyStore;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -110,7 +117,6 @@ public class Vpn {
private LegacyVpnRunner mLegacyVpnRunner;
private PendingIntent mStatusIntent;
private volatile boolean mEnableTeardown = true;
- private final IConnectivityManager mConnService;
private final INetworkManagementService mNetd;
private VpnConfig mConfig;
private NetworkAgent mNetworkAgent;
@@ -126,10 +132,9 @@ public class Vpn {
private final int mUserHandle;
public Vpn(Looper looper, Context context, INetworkManagementService netService,
- IConnectivityManager connService, int userHandle) {
+ int userHandle) {
mContext = context;
mNetd = netService;
- mConnService = connService;
mUserHandle = userHandle;
mLooper = looper;
@@ -336,6 +341,10 @@ public class Vpn {
return mNetworkInfo;
}
+ public int getNetId() {
+ return mNetworkAgent != null ? mNetworkAgent.netId : NETID_UNSET;
+ }
+
private LinkProperties makeLinkProperties() {
boolean allowIPv4 = mConfig.allowIPv4;
boolean allowIPv6 = mConfig.allowIPv6;
@@ -1106,11 +1115,15 @@ public class Vpn {
// registering
mOuterInterface = mConfig.interfaze;
- try {
- mOuterConnection.set(
- mConnService.findConnectionTypeForIface(mOuterInterface));
- } catch (Exception e) {
- mOuterConnection.set(ConnectivityManager.TYPE_NONE);
+ if (!TextUtils.isEmpty(mOuterInterface)) {
+ final ConnectivityManager cm = ConnectivityManager.from(mContext);
+ for (Network network : cm.getAllNetworks()) {
+ final LinkProperties lp = cm.getLinkProperties(network);
+ if (lp != null && mOuterInterface.equals(lp.getInterfaceName())) {
+ final NetworkInfo networkInfo = cm.getNetworkInfo(network);
+ if (networkInfo != null) mOuterConnection.set(networkInfo.getType());
+ }
+ }
}
IntentFilter filter = new IntentFilter();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a260b0ed3667..c12545b36d82 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6280,7 +6280,8 @@ public class PackageManagerService extends IPackageManager.Stub {
!VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) {
final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
for (int userId : userIds) {
- if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) {
+ if (mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName,
+ nativeLibPath, userId) < 0) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Failed linking native library dir (user=" + userId + ")");
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index d4c5f8716c0e..ac79b36f7005 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -40,6 +40,8 @@ public class WebViewUpdateService extends SystemService {
private boolean mRelroReady32Bit = false;
private boolean mRelroReady64Bit = false;
+ private String oldWebViewPackageName = null;
+
private BroadcastReceiver mWebViewUpdatedReceiver;
public WebViewUpdateService(Context context) {
@@ -51,9 +53,22 @@ public class WebViewUpdateService extends SystemService {
mWebViewUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String webviewPackage = "package:" + WebViewFactory.getWebViewPackageName();
- if (webviewPackage.equals(intent.getDataString())) {
- onWebViewUpdateInstalled();
+
+ for (String packageName : WebViewFactory.getWebViewPackageNames()) {
+ String webviewPackage = "package:" + packageName;
+
+ if (webviewPackage.equals(intent.getDataString())) {
+ String usedPackageName =
+ WebViewFactory.findPreferredWebViewPackage().packageName;
+ // Only trigger update actions if the updated package is the one that
+ // will be used, or the one that was in use before the update.
+ if (packageName.equals(usedPackageName) ||
+ packageName.equals(oldWebViewPackageName)) {
+ onWebViewUpdateInstalled();
+ oldWebViewPackageName = usedPackageName;
+ }
+ return;
+ }
}
}
};
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 719dd76a9e05..33a7fe10b0bc 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -203,14 +203,21 @@ public final class Call {
*/
public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00040000;
- /**
- * Call type can be modified for IMS call
+ /**
+ * Call can be upgraded to a video call.
* @hide
*/
public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 0x00080000;
+ /**
+ * For video calls, indicates whether the outgoing video for the call can be paused using
+ * the {@link android.telecom.VideoProfile.VideoState#PAUSED} VideoState.
+ * @hide
+ */
+ public static final int CAPABILITY_CAN_PAUSE_VIDEO = 0x00100000;
+
//******************************************************************************************
- // Next CAPABILITY value: 0x00100000
+ // Next CAPABILITY value: 0x00200000
//******************************************************************************************
private final Uri mHandle;
@@ -315,6 +322,9 @@ public final class Call {
if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO");
}
+ if (can(capabilities, CAPABILITY_CAN_PAUSE_VIDEO)) {
+ builder.append(" CAPABILITY_CAN_PAUSE_VIDEO");
+ }
builder.append("]");
return builder.toString();
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 3a54b1c2e008..476203196344 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -184,13 +184,20 @@ public abstract class Connection implements IConferenceable {
public static final int CAPABILITY_SPEED_UP_MT_AUDIO = 0x00040000;
/**
- * Call type can be modified for IMS call
+ * Call can be upgraded to a video call.
* @hide
*/
public static final int CAPABILITY_CAN_UPGRADE_TO_VIDEO = 0x00080000;
+ /**
+ * For video calls, indicates whether the outgoing video for the call can be paused using
+ * the {@link android.telecom.VideoProfile.VideoState#PAUSED} VideoState.
+ * @hide
+ */
+ public static final int CAPABILITY_CAN_PAUSE_VIDEO = 0x00100000;
+
//**********************************************************************************************
- // Next CAPABILITY value: 0x00100000
+ // Next CAPABILITY value: 0x00200000
//**********************************************************************************************
/**
@@ -342,6 +349,9 @@ public abstract class Connection implements IConferenceable {
if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO");
}
+ if (can(capabilities, CAPABILITY_CAN_PAUSE_VIDEO)) {
+ builder.append(" CAPABILITY_CAN_PAUSE_VIDEO");
+ }
builder.append("]");
return builder.toString();
}
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 611dd7bd512b..d19228896c5c 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -219,6 +219,15 @@ public class PhoneStateListener {
*/
public static final int LISTEN_OEM_HOOK_RAW_EVENT = 0x00008000;
+ /**
+ * Listen for carrier network changes indicated by a carrier app.
+ *
+ * @see #onCarrierNetworkRequest
+ * @see TelephonyManager#notifyCarrierNetworkChange(boolean)
+ * @hide
+ */
+ public static final int LISTEN_CARRIER_NETWORK_CHANGE = 0x00010000;
+
/*
* Subscription used to listen to the phone state changes
* @hide
@@ -321,6 +330,9 @@ public class PhoneStateListener {
case LISTEN_OEM_HOOK_RAW_EVENT:
PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj);
break;
+ case LISTEN_CARRIER_NETWORK_CHANGE:
+ PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
+ break;
}
}
@@ -500,6 +512,22 @@ public class PhoneStateListener {
}
/**
+ * Callback invoked when telephony has received notice from a carrier
+ * app that a network action that could result in connectivity loss
+ * has been requested by an app using
+ * {@link android.telephony.TelephonyManager#notifyCarrierNetworkChange(boolean)}
+ *
+ * @param active Whether the carrier network change is or shortly
+ * will be active. This value is true to indicate
+ * showing alternative UI and false to stop.
+ *
+ * @hide
+ */
+ public void onCarrierNetworkChange(boolean active) {
+ // default implementation empty
+ }
+
+ /**
* The callback methods need to be called on the handler thread where
* this object was created. If the binder did that for us it'd be nice.
*/
@@ -575,6 +603,10 @@ public class PhoneStateListener {
public void onOemHookRawEvent(byte[] rawData) {
Message.obtain(mHandler, LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData).sendToTarget();
}
+
+ public void onCarrierNetworkChange(boolean active) {
+ Message.obtain(mHandler, LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active).sendToTarget();
+ }
};
private void log(String s) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4486c95efbd2..128f6e3c1eea 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2039,6 +2039,35 @@ public class TelephonyManager {
}
/**
+ * Informs the system of an intentional upcoming carrier network change by
+ * a carrier app. This call is optional and is only used to allow the
+ * system to provide alternative UI while telephony is performing an action
+ * that may result in intentional, temporary network lack of connectivity.
+ * <p>
+ * Based on the active parameter passed in, this method will either show or
+ * hide the alternative UI. There is no timeout associated with showing
+ * this UX, so a carrier app must be sure to call with active set to false
+ * sometime after calling with it set to true.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ * Or the calling app has carrier privileges.
+ * @see #hasCarrierPrivileges
+ *
+ * @param active Whether the carrier network change is or shortly will be
+ * active. Set this value to true to begin showing
+ * alternative UI and false to stop.
+ */
+ public void notifyCarrierNetworkChange(boolean active) {
+ try {
+ if (sRegistry != null)
+ sRegistry.notifyCarrierNetworkChange(active);
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ /**
* Returns the alphabetic identifier associated with the line 1 number.
* Return null if it is unavailable.
* <p>
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
index cea62babe9a8..cbedb95d2ea5 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -44,5 +44,6 @@ oneway interface IPhoneStateListener {
void onDataConnectionRealTimeInfoChanged(in DataConnectionRealTimeInfo dcRtInfo);
void onVoLteServiceStateChanged(in VoLteServiceState lteState);
void onOemHookRawEvent(in byte[] rawData);
+ void onCarrierNetworkChange(in boolean active);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 7d8a8d66582e..76b69cea8ae6 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -69,4 +69,5 @@ interface ITelephonyRegistry {
void notifyVoLteServiceStateChanged(in VoLteServiceState lteState);
void notifyOemHookRawEventForSubscriber(in int subId, in byte[] rawData);
void notifySubscriptionInfoChanged();
+ void notifyCarrierNetworkChange(in boolean active);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 082e8bbb3ff9..12541d8e6aaa 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -69,6 +69,14 @@ public interface RILConstants {
int SS_MODIFIED_TO_USSD = 25; /* SS request modified to USSD */
int SUBSCRIPTION_NOT_SUPPORTED = 26; /* Subscription not supported */
int SS_MODIFIED_TO_SS = 27; /* SS request modified to different SS request */
+ int SIM_ALREADY_POWERED_OFF = 29; /* SAP: 0x03, Error card aleready powered off */
+ int SIM_ALREADY_POWERED_ON = 30; /* SAP: 0x05, Error card already powered on */
+ int SIM_DATA_NOT_AVAILABLE = 31; /* SAP: 0x06, Error data not available */
+ int SIM_SAP_CONNECT_FAILURE = 32;
+ int SIM_SAP_MSG_SIZE_TOO_LARGE = 33;
+ int SIM_SAP_MSG_SIZE_TOO_SMALL = 34;
+ int SIM_SAP_CONNECT_OK_CALL_ONGOING = 35;
+ int LCE_NOT_SUPPORTED = 36; /* Link Capacity Estimation (LCE) not supported */
/* NETWORK_MODE_* See ril.h RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE */
@@ -135,6 +143,11 @@ public interface RILConstants {
int NV_CONFIG_ERASE_RESET = 2;
int NV_CONFIG_FACTORY_RESET = 3;
+ /* LCE service related constants. */
+ int LCE_NOT_AVAILABLE = -1;
+ int LCE_STOPPED = 0;
+ int LCE_ACTIVE = 1;
+
/*
cat include/telephony/ril.h | \
egrep '^#define' | \
@@ -307,6 +320,9 @@ cat include/telephony/ril.h | \
int RIL_REQUEST_SHUTDOWN = 129;
int RIL_REQUEST_GET_RADIO_CAPABILITY = 130;
int RIL_REQUEST_SET_RADIO_CAPABILITY = 131;
+ int RIL_REQUEST_START_LCE = 132;
+ int RIL_REQUEST_STOP_LCE = 133;
+ int RIL_REQUEST_PULL_LCEDATA = 134;
int RIL_UNSOL_RESPONSE_BASE = 1000;
int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000;
@@ -354,4 +370,5 @@ cat include/telephony/ril.h | \
int RIL_UNSOL_RADIO_CAPABILITY = 1042;
int RIL_UNSOL_ON_SS = 1043;
int RIL_UNSOL_STK_CC_ALPHA_NOTIFY = 1044;
+ int RIL_UNSOL_LCEDATA_RECV = 1045;
}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index e78750cc05be..5a8c7ff9b749 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -16,6 +16,7 @@
package android.test.mock;
+import android.annotation.NonNull;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
@@ -51,6 +52,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
import java.util.List;
@@ -498,6 +500,18 @@ public class MockPackageManager extends PackageManager {
throw new UnsupportedOperationException();
}
+ /** {@hide} */
+ @Override
+ public @NonNull VolumeInfo getApplicationCurrentVolume(ApplicationInfo app) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
+ public @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public String getInstallerPackageName(String packageName) {
throw new UnsupportedOperationException();
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 0622dc6288c1..05034c395e3a 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -27,6 +27,7 @@ main := Main.cpp
sources := \
BigBuffer.cpp \
BinaryResourceParser.cpp \
+ BinaryXmlPullParser.cpp \
BindingXmlPullParser.cpp \
ConfigDescription.cpp \
Files.cpp \
@@ -51,7 +52,9 @@ sources := \
ScopedXmlPullParser.cpp \
SourceXmlPullParser.cpp \
XliffXmlPullParser.cpp \
- XmlFlattener.cpp
+ XmlFlattener.cpp \
+ ZipEntry.cpp \
+ ZipFile.cpp
testSources := \
BigBuffer_test.cpp \
@@ -63,6 +66,7 @@ testSources := \
Locale_test.cpp \
ManifestParser_test.cpp \
Maybe_test.cpp \
+ NameMangler_test.cpp \
ResourceParser_test.cpp \
Resource_test.cpp \
ResourceTable_test.cpp \
diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp
index 3eb96bcaaa57..71016c18e974 100644
--- a/tools/aapt2/BinaryResourceParser.cpp
+++ b/tools/aapt2/BinaryResourceParser.cpp
@@ -17,6 +17,7 @@
#include "BinaryResourceParser.h"
#include "Logger.h"
#include "ResChunkPullParser.h"
+#include "Resolver.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
#include "ResourceTypeExtensions.h"
@@ -33,28 +34,14 @@ namespace aapt {
using namespace android;
-template <typename T>
-inline static const T* convertTo(const ResChunk_header* chunk) {
- if (chunk->headerSize < sizeof(T)) {
- return nullptr;
- }
- return reinterpret_cast<const T*>(chunk);
-}
-
-inline static const uint8_t* getChunkData(const ResChunk_header& chunk) {
- return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
-}
-
-inline static size_t getChunkDataLen(const ResChunk_header& chunk) {
- return chunk.size - chunk.headerSize;
-}
-
/*
* Visitor that converts a reference's resource ID to a resource name,
* given a mapping from resource ID to resource name.
*/
struct ReferenceIdToNameVisitor : ValueVisitor {
- ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>& cache) : mCache(cache) {
+ ReferenceIdToNameVisitor(const std::shared_ptr<Resolver>& resolver,
+ std::map<ResourceId, ResourceName>* cache) :
+ mResolver(resolver), mCache(cache) {
}
void visit(Reference& reference, ValueVisitorArgs&) override {
@@ -104,24 +91,39 @@ private:
return;
}
- auto cacheIter = mCache.find(reference.id);
- if (cacheIter == std::end(mCache)) {
- Logger::note() << "failed to find " << reference.id << std::endl;
- } else {
+ auto cacheIter = mCache->find(reference.id);
+ if (cacheIter != mCache->end()) {
reference.name = cacheIter->second;
reference.id = 0;
+ } else {
+ const android::ResTable& table = mResolver->getResTable();
+ android::ResTable::resource_name resourceName;
+ if (table.getResourceName(reference.id.id, false, &resourceName)) {
+ const ResourceType* type = parseResourceType(StringPiece16(resourceName.type,
+ resourceName.typeLen));
+ assert(type);
+ reference.name.package.assign(resourceName.package, resourceName.packageLen);
+ reference.name.type = *type;
+ reference.name.entry.assign(resourceName.name, resourceName.nameLen);
+ reference.id = 0;
+
+ // Add to cache.
+ mCache->insert({reference.id, reference.name});
+ }
}
}
- const std::map<ResourceId, ResourceName>& mCache;
+ std::shared_ptr<Resolver> mResolver;
+ std::map<ResourceId, ResourceName>* mCache;
};
-BinaryResourceParser::BinaryResourceParser(std::shared_ptr<ResourceTable> table,
+BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver,
const Source& source,
const void* data,
size_t len) :
- mTable(table), mSource(source), mData(data), mDataLen(len) {
+ mTable(table), mResolver(resolver), mSource(source), mData(data), mDataLen(len) {
}
bool BinaryResourceParser::parse() {
@@ -421,7 +423,7 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
// Now go through the table and change resource ID references to
// symbolic references.
- ReferenceIdToNameVisitor visitor(mIdIndex);
+ ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex);
for (auto& type : *mTable) {
for (auto& entry : type->entries) {
for (auto& configValue : entry->values) {
@@ -676,7 +678,8 @@ std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef
std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
const ConfigDescription& config,
const ResTable_map_entry* map) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0;
+ std::unique_ptr<Style> style = util::make_unique<Style>(isWeak);
if (map->parent.ident == 0) {
// The parent is either not set or it is an unresolved symbol.
// Check to see if it is a symbol.
@@ -759,7 +762,17 @@ std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNa
const ResTable_map_entry* map) {
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
for (const ResTable_map& mapEntry : map) {
- styleable->entries.emplace_back(mapEntry.name.ident);
+ if (mapEntry.name.ident == 0) {
+ // The map entry's key (attribute) is not set. This must be
+ // a symbol reference, so resolve it.
+ ResourceNameRef symbol;
+ bool result = getSymbol(&mapEntry.name.ident, &symbol);
+ assert(result);
+ styleable->entries.emplace_back(symbol);
+ } else {
+ // The map entry's key (attribute) is a regular reference.
+ styleable->entries.emplace_back(mapEntry.name.ident);
+ }
}
return styleable;
}
diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/BinaryResourceParser.h
index 92680786d9eb..f95a0c8b6a68 100644
--- a/tools/aapt2/BinaryResourceParser.h
+++ b/tools/aapt2/BinaryResourceParser.h
@@ -17,6 +17,7 @@
#ifndef AAPT_BINARY_RESOURCE_PARSER_H
#define AAPT_BINARY_RESOURCE_PARSER_H
+#include "Resolver.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "Source.h"
@@ -41,7 +42,9 @@ public:
* Creates a parser, which will read `len` bytes from `data`, and
* add any resources parsed to `table`. `source` is for logging purposes.
*/
- BinaryResourceParser(std::shared_ptr<ResourceTable> table, const Source& source,
+ BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver,
+ const Source& source,
const void* data, size_t len);
BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
@@ -89,6 +92,8 @@ private:
std::shared_ptr<ResourceTable> mTable;
+ std::shared_ptr<Resolver> mResolver;
+
const Source mSource;
const void* mData;
diff --git a/tools/aapt2/BinaryXmlPullParser.cpp b/tools/aapt2/BinaryXmlPullParser.cpp
new file mode 100644
index 000000000000..7a07c06c41e1
--- /dev/null
+++ b/tools/aapt2/BinaryXmlPullParser.cpp
@@ -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.
+ */
+
+#include "BinaryXmlPullParser.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+static XmlPullParser::Event codeToEvent(android::ResXMLParser::event_code_t code) {
+ switch (code) {
+ case android::ResXMLParser::START_DOCUMENT:
+ return XmlPullParser::Event::kStartDocument;
+ case android::ResXMLParser::END_DOCUMENT:
+ return XmlPullParser::Event::kEndDocument;
+ case android::ResXMLParser::START_NAMESPACE:
+ return XmlPullParser::Event::kStartNamespace;
+ case android::ResXMLParser::END_NAMESPACE:
+ return XmlPullParser::Event::kEndNamespace;
+ case android::ResXMLParser::START_TAG:
+ return XmlPullParser::Event::kStartElement;
+ case android::ResXMLParser::END_TAG:
+ return XmlPullParser::Event::kEndElement;
+ case android::ResXMLParser::TEXT:
+ return XmlPullParser::Event::kText;
+ default:
+ break;
+ }
+ return XmlPullParser::Event::kBadDocument;
+}
+
+BinaryXmlPullParser::BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser)
+ : mParser(parser), mEvent(Event::kStartDocument), mHasComment(false), sEmpty(), sEmpty8(),
+ mDepth(0) {
+}
+
+XmlPullParser::Event BinaryXmlPullParser::next() {
+ mStr1.clear();
+ mStr2.clear();
+ mAttributes.clear();
+
+ android::ResXMLParser::event_code_t code;
+ if (mHasComment) {
+ mHasComment = false;
+ code = mParser->getEventType();
+ } else {
+ code = mParser->next();
+ if (code != android::ResXMLParser::BAD_DOCUMENT) {
+ size_t len;
+ const char16_t* comment = mParser->getComment(&len);
+ if (comment) {
+ mHasComment = true;
+ mStr1.assign(comment, len);
+ return XmlPullParser::Event::kComment;
+ }
+ }
+ }
+
+ size_t len;
+ const char16_t* data;
+ mEvent = codeToEvent(code);
+ switch (mEvent) {
+ case Event::kStartNamespace:
+ case Event::kEndNamespace:
+ data = mParser->getNamespacePrefix(&len);
+ mStr1.assign(data, len);
+ data = mParser->getNamespaceUri(&len);
+ mStr2.assign(data, len);
+ break;
+
+ case Event::kStartElement:
+ copyAttributes();
+ // fallthrough
+
+ case Event::kEndElement:
+ data = mParser->getElementNamespace(&len);
+ mStr1.assign(data, len);
+ data = mParser->getElementName(&len);
+ mStr2.assign(data, len);
+ break;
+
+ case Event::kText:
+ data = mParser->getText(&len);
+ mStr1.assign(data, len);
+ break;
+
+ default:
+ break;
+ }
+ return mEvent;
+}
+
+XmlPullParser::Event BinaryXmlPullParser::getEvent() const {
+ if (mHasComment) {
+ return XmlPullParser::Event::kComment;
+ }
+ return mEvent;
+}
+
+const std::string& BinaryXmlPullParser::getLastError() const {
+ return sEmpty8;
+}
+
+const std::u16string& BinaryXmlPullParser::getComment() const {
+ if (mHasComment) {
+ return mStr1;
+ }
+ return sEmpty;
+}
+
+size_t BinaryXmlPullParser::getLineNumber() const {
+ return mParser->getLineNumber();
+}
+
+size_t BinaryXmlPullParser::getDepth() const {
+ return mDepth;
+}
+
+const std::u16string& BinaryXmlPullParser::getText() const {
+ if (!mHasComment && mEvent == XmlPullParser::Event::kText) {
+ return mStr1;
+ }
+ return sEmpty;
+}
+
+const std::u16string& BinaryXmlPullParser::getNamespacePrefix() const {
+ if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace ||
+ mEvent == XmlPullParser::Event::kEndNamespace)) {
+ return mStr1;
+ }
+ return sEmpty;
+}
+
+const std::u16string& BinaryXmlPullParser::getNamespaceUri() const {
+ if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace ||
+ mEvent == XmlPullParser::Event::kEndNamespace)) {
+ return mStr2;
+ }
+ return sEmpty;
+}
+
+const std::u16string& BinaryXmlPullParser::getElementNamespace() const {
+ if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement ||
+ mEvent == XmlPullParser::Event::kEndElement)) {
+ return mStr1;
+ }
+ return sEmpty;
+}
+
+const std::u16string& BinaryXmlPullParser::getElementName() const {
+ if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement ||
+ mEvent == XmlPullParser::Event::kEndElement)) {
+ return mStr2;
+ }
+ return sEmpty;
+}
+
+size_t BinaryXmlPullParser::getAttributeCount() const {
+ return mAttributes.size();
+}
+
+XmlPullParser::const_iterator BinaryXmlPullParser::beginAttributes() const {
+ return mAttributes.begin();
+}
+
+XmlPullParser::const_iterator BinaryXmlPullParser::endAttributes() const {
+ return mAttributes.end();
+}
+
+void BinaryXmlPullParser::copyAttributes() {
+ const size_t attrCount = mParser->getAttributeCount();
+ if (attrCount > 0) {
+ mAttributes.reserve(attrCount);
+ for (size_t i = 0; i < attrCount; i++) {
+ XmlPullParser::Attribute attr;
+ size_t len;
+ const char16_t* str = mParser->getAttributeNamespace(i, &len);
+ attr.namespaceUri.assign(str, len);
+ str = mParser->getAttributeName(i, &len);
+ attr.name.assign(str, len);
+ str = mParser->getAttributeStringValue(i, &len);
+ attr.value.assign(str, len);
+ mAttributes.push_back(std::move(attr));
+ }
+ }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BinaryXmlPullParser.h b/tools/aapt2/BinaryXmlPullParser.h
new file mode 100644
index 000000000000..2d4256a17496
--- /dev/null
+++ b/tools/aapt2/BinaryXmlPullParser.h
@@ -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.
+ */
+
+#ifndef AAPT_BINARY_XML_PULL_PARSER_H
+#define AAPT_BINARY_XML_PULL_PARSER_H
+
+#include "XmlPullParser.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * Wraps a ResTable into the canonical XmlPullParser interface.
+ */
+class BinaryXmlPullParser : public XmlPullParser {
+public:
+ BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser);
+ BinaryXmlPullParser(const BinaryXmlPullParser& rhs) = delete;
+
+ Event getEvent() const;
+ const std::string& getLastError() const;
+ Event next();
+
+ const std::u16string& getComment() const;
+ size_t getLineNumber() const;
+ size_t getDepth() const;
+
+ const std::u16string& getText() const;
+
+ const std::u16string& getNamespacePrefix() const;
+ const std::u16string& getNamespaceUri() const;
+
+ const std::u16string& getElementNamespace() const;
+ const std::u16string& getElementName() const;
+
+ const_iterator beginAttributes() const;
+ const_iterator endAttributes() const;
+ size_t getAttributeCount() const;
+
+private:
+ void copyAttributes();
+
+ std::shared_ptr<android::ResXMLTree> mParser;
+ std::u16string mStr1;
+ std::u16string mStr2;
+ std::vector<Attribute> mAttributes;
+ Event mEvent;
+ bool mHasComment;
+ const std::u16string sEmpty;
+ const std::string sEmpty8;
+ size_t mDepth;
+};
+
+} // namespace aapt
+
+#endif // AAPT_BINARY_XML_PULL_PARSER_H
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
index b1ee8e7bcd4c..3b2ff510bb62 100644
--- a/tools/aapt2/Flag.cpp
+++ b/tools/aapt2/Flag.cpp
@@ -16,6 +16,7 @@ struct Flag {
std::function<void(const StringPiece&)> action;
bool required;
bool* flagResult;
+ bool flagValueWhenSet;
bool parsed;
};
@@ -25,21 +26,22 @@ static std::vector<std::string> sArgs;
void optionalFlag(const StringPiece& name, const StringPiece& description,
std::function<void(const StringPiece&)> action) {
sFlags.push_back(
- Flag{ name.toString(), description.toString(), action, false, nullptr, false });
+ Flag{ name.toString(), description.toString(), action, false, nullptr, false, false });
}
void requiredFlag(const StringPiece& name, const StringPiece& description,
std::function<void(const StringPiece&)> action) {
sFlags.push_back(
- Flag{ name.toString(), description.toString(), action, true, nullptr, false });
+ Flag{ name.toString(), description.toString(), action, true, nullptr, false, false });
}
-void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result) {
- sFlags.push_back(
- Flag{ name.toString(), description.toString(), {}, false, result, false });
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
+ bool* result) {
+ sFlags.push_back(Flag{
+ name.toString(), description.toString(), {}, false, result, resultWhenSet, false });
}
-static void usageAndDie(const StringPiece& command) {
+void usageAndDie(const StringPiece& command) {
std::cerr << command << " [options]";
for (const Flag& flag : sFlags) {
if (flag.required) {
@@ -73,7 +75,7 @@ void parse(int argc, char** argv, const StringPiece& command) {
match = true;
flag.parsed = true;
if (flag.flagResult) {
- *flag.flagResult = true;
+ *flag.flagResult = flag.flagValueWhenSet;
} else {
i++;
if (i >= argc) {
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
index 32f5f2cb61cb..4745c355d8cd 100644
--- a/tools/aapt2/Flag.h
+++ b/tools/aapt2/Flag.h
@@ -16,7 +16,10 @@ void requiredFlag(const StringPiece& name, const StringPiece& description,
void optionalFlag(const StringPiece& name, const StringPiece& description,
std::function<void(const StringPiece&)> action);
-void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result);
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet,
+ bool* result);
+
+void usageAndDie(const StringPiece& command);
void parse(int argc, char** argv, const StringPiece& command);
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
index 779a346f9289..3f92f18d29c1 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/JavaClassGenerator.cpp
@@ -15,6 +15,7 @@
*/
#include "JavaClassGenerator.h"
+#include "NameMangler.h"
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
@@ -31,7 +32,7 @@ namespace aapt {
// The number of attributes to emit per line in a Styleable array.
constexpr size_t kAttribsPerLine = 4;
-JavaClassGenerator::JavaClassGenerator(std::shared_ptr<const ResourceTable> table,
+JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table,
Options options) :
mTable(table), mOptions(options) {
}
@@ -79,42 +80,18 @@ static std::u16string transform(const StringPiece16& symbol) {
return output;
}
-bool JavaClassGenerator::generateType(std::ostream& out, const ResourceTableType& type,
- size_t packageId) {
- const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
-
- for (const auto& entry : type.entries) {
- ResourceId id = { packageId, type.typeId, entry->entryId };
- assert(id.isValid());
-
- if (!isValidSymbol(entry->name)) {
- std::stringstream err;
- err << "invalid symbol name '"
- << StringPiece16(entry->name)
- << "'";
- mError = err.str();
- return false;
- }
-
- out << " "
- << "public static" << finalModifier
- << " int " << transform(entry->name) << " = " << id << ";" << std::endl;
- }
- return true;
-}
-
struct GenArgs : ValueVisitorArgs {
- GenArgs(std::ostream& o, const ResourceEntry& e) : out(o), entry(e) {
+ GenArgs(std::ostream* o, std::u16string* e) : out(o), entryName(e) {
}
- std::ostream& out;
- const ResourceEntry& entry;
+ std::ostream* out;
+ std::u16string* entryName;
};
void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) {
const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
- std::ostream& out = static_cast<GenArgs&>(a).out;
- const ResourceEntry& entry = static_cast<GenArgs&>(a).entry;
+ std::ostream* out = static_cast<GenArgs&>(a).out;
+ std::u16string* entryName = static_cast<GenArgs&>(a).entryName;
// This must be sorted by resource ID.
std::vector<std::pair<ResourceId, StringPiece16>> sortedAttributes;
@@ -127,59 +104,86 @@ void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a)
std::sort(sortedAttributes.begin(), sortedAttributes.end());
// First we emit the array containing the IDs of each attribute.
- out << " "
- << "public static final int[] " << transform(entry.name) << " = {";
+ *out << " "
+ << "public static final int[] " << transform(*entryName) << " = {";
const size_t attrCount = sortedAttributes.size();
for (size_t i = 0; i < attrCount; i++) {
if (i % kAttribsPerLine == 0) {
- out << std::endl << " ";
+ *out << std::endl << " ";
}
- out << sortedAttributes[i].first;
+ *out << sortedAttributes[i].first;
if (i != attrCount - 1) {
- out << ", ";
+ *out << ", ";
}
}
- out << std::endl << " };" << std::endl;
+ *out << std::endl << " };" << std::endl;
// Now we emit the indices into the array.
for (size_t i = 0; i < attrCount; i++) {
- out << " "
- << "public static" << finalModifier
- << " int " << transform(entry.name) << "_" << transform(sortedAttributes[i].second)
- << " = " << i << ";" << std::endl;
+ *out << " "
+ << "public static" << finalModifier
+ << " int " << transform(*entryName) << "_" << transform(sortedAttributes[i].second)
+ << " = " << i << ";" << std::endl;
}
}
-bool JavaClassGenerator::generate(std::ostream& out) {
+bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId,
+ const ResourceTableType& type, std::ostream& out) {
+ const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
+
+ std::u16string unmangledPackage;
+ std::u16string unmangledName;
+ for (const auto& entry : type.entries) {
+ ResourceId id = { packageId, type.typeId, entry->entryId };
+ assert(id.isValid());
+
+ unmangledName = entry->name;
+ if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
+ // The entry name was mangled, and we successfully unmangled it.
+ // Check that we want to emit this symbol.
+ if (package != unmangledPackage) {
+ // Skip the entry if it doesn't belong to the package we're writing.
+ continue;
+ }
+ } else {
+ if (package != mTable->getPackage()) {
+ // We are processing a mangled package name,
+ // but this is a non-mangled resource.
+ continue;
+ }
+ }
+
+ if (!isValidSymbol(unmangledName)) {
+ ResourceNameRef resourceName = { package, type.type, unmangledName };
+ std::stringstream err;
+ err << "invalid symbol name '" << resourceName << "'";
+ mError = err.str();
+ return false;
+ }
+
+ if (type.type == ResourceType::kStyleable) {
+ assert(!entry->values.empty());
+ entry->values.front().value->accept(*this, GenArgs{ &out, &unmangledName });
+ } else {
+ out << " " << "public static" << finalModifier
+ << " int " << transform(unmangledName) << " = " << id << ";" << std::endl;
+ }
+ }
+ return true;
+}
+
+bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) {
const size_t packageId = mTable->getPackageId();
- generateHeader(out, mTable->getPackage());
+ generateHeader(out, package);
out << "public final class R {" << std::endl;
for (const auto& type : *mTable) {
out << " public static final class " << type->type << " {" << std::endl;
- bool result;
- if (type->type == ResourceType::kStyleable) {
- for (const auto& entry : type->entries) {
- assert(!entry->values.empty());
- if (!isValidSymbol(entry->name)) {
- std::stringstream err;
- err << "invalid symbol name '"
- << StringPiece16(entry->name)
- << "'";
- mError = err.str();
- return false;
- }
- entry->values.front().value->accept(*this, GenArgs{ out, *entry });
- }
- } else {
- result = generateType(out, *type, packageId);
- }
-
- if (!result) {
+ if (!generateType(package, packageId, *type, out)) {
return false;
}
out << " }" << std::endl;
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h
index 5b8e50035a9c..f8b9ee3f1fc8 100644
--- a/tools/aapt2/JavaClassGenerator.h
+++ b/tools/aapt2/JavaClassGenerator.h
@@ -41,12 +41,16 @@ public:
bool useFinal = true;
};
- JavaClassGenerator(std::shared_ptr<const ResourceTable> table, Options options);
+ JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options);
/*
- * Writes the R.java file to `out`. Returns true on success.
+ * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
+ * All symbols technically belong to a single package, but linked libraries will
+ * have their names mangled, denoting that they came from a different package.
+ * We need to generate these symbols in a separate file.
+ * Returns true on success.
*/
- bool generate(std::ostream& out);
+ bool generate(const std::u16string& package, std::ostream& out);
/*
* ConstValueVisitor implementation.
@@ -56,7 +60,8 @@ public:
const std::string& getError() const;
private:
- bool generateType(std::ostream& out, const ResourceTableType& type, size_t packageId);
+ bool generateType(const std::u16string& package, size_t packageId,
+ const ResourceTableType& type, std::ostream& out);
std::shared_ptr<const ResourceTable> mTable;
Options mOptions;
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index 32050e30fd8a..96bb10b9232c 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -15,6 +15,8 @@
*/
#include "JavaClassGenerator.h"
+#include "Linker.h"
+#include "Resolver.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "Util.h"
@@ -47,7 +49,7 @@ TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
JavaClassGenerator generator(mTable, {});
std::stringstream out;
- EXPECT_FALSE(generator.generate(out));
+ EXPECT_FALSE(generator.generate(mTable->getPackage(), out));
}
TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
@@ -69,7 +71,7 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
JavaClassGenerator generator(mTable, {});
std::stringstream out;
- EXPECT_TRUE(generator.generate(out));
+ EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
std::string output = out.str();
EXPECT_NE(std::string::npos,
@@ -82,4 +84,33 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
output.find("public static final int hey_dude_cool_attr = 0;"));
}
+TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
+ ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" },
+ ResourceId{ 0x01, 0x02, 0x0000 }));
+ ResourceTable table;
+ table.setPackage(u"com.lib");
+ ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
+ SourceLine{ "lib.xml", 33 }, util::make_unique<Id>()));
+ ASSERT_TRUE(mTable->merge(std::move(table)));
+
+ std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(mTable,
+ std::make_shared<const android::AssetManager>());
+ Linker linker(mTable, resolver);
+ ASSERT_TRUE(linker.linkAndValidate());
+
+ JavaClassGenerator generator(mTable, {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int foo ="));
+ EXPECT_EQ(std::string::npos, output.find("int test ="));
+
+ out.str("");
+ EXPECT_TRUE(generator.generate(u"com.lib", out));
+ output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int test ="));
+ EXPECT_EQ(std::string::npos, output.find("int foo ="));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp
index 1cfb2975b1dd..4346c8bb35b2 100644
--- a/tools/aapt2/Linker.cpp
+++ b/tools/aapt2/Linker.cpp
@@ -128,6 +128,20 @@ const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const {
void Linker::visit(Reference& reference, ValueVisitorArgs& a) {
Args& args = static_cast<Args&>(a);
+ if (!reference.name.isValid()) {
+ // We can't have a completely bad reference.
+ assert(reference.id.isValid());
+
+ // This reference has no name but has an ID.
+ // It is a really bad error to have no name and have the same
+ // package ID.
+ assert(reference.id.packageId() != mTable->getPackageId());
+
+ // The reference goes outside this package, let it stay as a
+ // resource ID because it will not change.
+ return;
+ }
+
Maybe<ResourceId> result = mResolver->findId(reference.name);
if (!result) {
addUnresolvedSymbol(reference.name, args.source);
@@ -206,7 +220,7 @@ void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine
void Linker::visit(Style& style, ValueVisitorArgs& a) {
Args& args = static_cast<Args&>(a);
- if (style.parent.name.isValid()) {
+ if (style.parent.name.isValid() || style.parent.id.isValid()) {
visit(style.parent, a);
}
diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp
index b1e201b3ec1b..4d2d360d441a 100644
--- a/tools/aapt2/Linker_test.cpp
+++ b/tools/aapt2/Linker_test.cpp
@@ -30,6 +30,7 @@ struct LinkerTest : public ::testing::Test {
virtual void SetUp() override {
mTable = std::make_shared<ResourceTable>();
mTable->setPackage(u"android");
+ mTable->setPackageId(0x01);
mLinker = std::make_shared<Linker>(mTable, std::make_shared<Resolver>(
mTable, std::make_shared<android::AssetManager>()));
@@ -75,7 +76,7 @@ TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) {
}
TEST_F(LinkerTest, EscapeAndConvertRawString) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
style->entries.push_back(Style::Entry{
ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123"))
@@ -91,7 +92,7 @@ TEST_F(LinkerTest, EscapeAndConvertRawString) {
}
TEST_F(LinkerTest, FailToConvertRawString) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
style->entries.push_back(Style::Entry{
ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?"))
@@ -103,7 +104,7 @@ TEST_F(LinkerTest, FailToConvertRawString) {
}
TEST_F(LinkerTest, ConvertRawStringToString) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
style->entries.push_back(Style::Entry{
ResourceNameRef{ u"android", ResourceType::kAttr, u"string" },
util::make_unique<RawString>(
@@ -122,7 +123,7 @@ TEST_F(LinkerTest, ConvertRawStringToString) {
}
TEST_F(LinkerTest, ConvertRawStringToFlags) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
style->entries.push_back(Style::Entry{
ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" },
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple"))
@@ -140,4 +141,12 @@ TEST_F(LinkerTest, ConvertRawStringToFlags) {
EXPECT_EQ(bin->value.data, 1u | 2u);
}
+TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) {
+ ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" },
+ util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 })));
+
+ ASSERT_TRUE(mLinker->linkAndValidate());
+ EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 87127fd3a75b..03b9ba4f86ce 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -17,6 +17,7 @@
#include "AppInfo.h"
#include "BigBuffer.h"
#include "BinaryResourceParser.h"
+#include "BinaryXmlPullParser.h"
#include "BindingXmlPullParser.h"
#include "Files.h"
#include "Flag.h"
@@ -34,6 +35,7 @@
#include "TableFlattener.h"
#include "Util.h"
#include "XmlFlattener.h"
+#include "ZipFile.h"
#include <algorithm>
#include <androidfw/AssetManager.h>
@@ -44,8 +46,11 @@
#include <iostream>
#include <sstream>
#include <sys/stat.h>
+#include <unordered_set>
#include <utils/Errors.h>
+constexpr const char* kAaptVersionStr = "2.0-alpha";
+
using namespace aapt;
void printTable(const ResourceTable& table) {
@@ -96,17 +101,6 @@ void printStringPool(const StringPool& pool) {
}
}
-std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringPiece& filename,
- ResourceType type, const ConfigDescription& config) {
- std::stringstream path;
- path << "res/" << type;
- if (config != ConfigDescription{}) {
- path << "-" << config;
- }
- path << "/" << filename;
- return util::make_unique<FileReference>(pool.makeRef(util::utf8ToUtf16(path.str())));
-}
-
/**
* Collect files from 'root', filtering out any files that do not
* match the FileFilter 'filter'.
@@ -148,30 +142,6 @@ bool walkTree(const Source& root, const FileFilter& filter,
return !error;
}
-bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source& source) {
- std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
- if (!ifs) {
- Logger::error(source) << strerror(errno) << std::endl;
- return false;
- }
-
- std::streampos fsize = ifs.tellg();
- ifs.seekg(0, std::ios::end);
- fsize = ifs.tellg() - fsize;
- ifs.seekg(0, std::ios::beg);
-
- assert(fsize >= 0);
- size_t dataSize = static_cast<size_t>(fsize);
- char* buf = new char[dataSize];
- ifs.read(buf, dataSize);
-
- BinaryResourceParser parser(table, source, buf, dataSize);
- bool result = parser.parse();
-
- delete [] buf;
- return result;
-}
-
bool loadResTable(android::ResTable* table, const Source& source) {
std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
if (!ifs) {
@@ -195,7 +165,7 @@ bool loadResTable(android::ResTable* table, const Source& source) {
return result;
}
-void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
+void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
for (auto& type : *table) {
if (type->type != ResourceType::kStyle) {
continue;
@@ -251,10 +221,12 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
{},
// Create a copy of the original style.
- std::unique_ptr<Value>(configValue.value->clone())
+ std::unique_ptr<Value>(configValue.value->clone(
+ &table->getValueStringPool()))
};
Style& newStyle = static_cast<Style&>(*value.value);
+ newStyle.weak = true;
// Move the recorded stripped attributes into this new style.
std::move(stripped.begin(), stripped.end(),
@@ -285,88 +257,106 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
}
}
-bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
- const ResourceName& name, const ConfigDescription& config) {
- std::ifstream in(source.path, std::ifstream::binary);
- if (!in) {
- Logger::error(source) << strerror(errno) << std::endl;
- return false;
+struct CompileItem {
+ Source source;
+ ResourceName name;
+ ConfigDescription config;
+ std::string extension;
+};
+
+struct LinkItem {
+ Source source;
+ std::string apkPath;
+};
+
+std::string buildFileReference(const CompileItem& item) {
+ std::stringstream path;
+ path << "res/" << item.name.type;
+ if (item.config != ConfigDescription{}) {
+ path << "-" << item.config;
}
+ path << "/" << util::utf16ToUtf8(item.name.entry) + "." + item.extension;
+ return path.str();
+}
- std::set<size_t> sdkLevels;
+bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
+ StringPool& pool = table->getValueStringPool();
+ StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)));
+ return table->addResource(item.name, item.config, item.source.line(0),
+ util::make_unique<FileReference>(ref));
+}
- SourceXmlPullParser parser(in);
- while (XmlPullParser::isGoodEvent(parser.next())) {
- if (parser.getEvent() != XmlPullParser::Event::kStartElement) {
- continue;
- }
+struct AaptOptions {
+ enum class Phase {
+ Link,
+ Compile,
+ };
- const auto endIter = parser.endAttributes();
- for (auto iter = parser.beginAttributes(); iter != endIter; ++iter) {
- if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") {
- size_t sdkLevel = findAttributeSdkLevel(iter->name);
- if (sdkLevel > 1) {
- sdkLevels.insert(sdkLevel);
- }
- }
+ // The phase to process.
+ Phase phase;
- ResourceNameRef refName;
- bool create = false;
- bool privateRef = false;
- if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) &&
- create) {
- table->addResource(refName, {}, source.line(parser.getLineNumber()),
- util::make_unique<Id>());
- }
- }
- }
+ // Details about the app.
+ AppInfo appInfo;
- for (size_t level : sdkLevels) {
- Logger::note(source)
- << "creating v" << level << " versioned file."
- << std::endl;
- ConfigDescription newConfig = config;
- newConfig.sdkVersion = level;
-
- std::unique_ptr<FileReference> fileResource = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(name.entry) + ".xml",
- name.type,
- newConfig);
- table->addResource(name, newConfig, source.line(0), std::move(fileResource));
- }
- return true;
-}
+ // The location of the manifest file.
+ Source manifest;
-struct CompileItem {
- Source source;
- ResourceName name;
- ConfigDescription config;
- std::string extension;
+ // The APK files to link.
+ std::vector<Source> input;
+
+ // The libraries these files may reference.
+ std::vector<Source> libraries;
+
+ // Output path. This can be a directory or file
+ // depending on the phase.
+ Source output;
+
+ // Directory in which to write binding xml files.
+ Source bindingOutput;
+
+ // Directory to in which to generate R.java.
+ Maybe<Source> generateJavaClass;
+
+ // Whether to output verbose details about
+ // compilation.
+ bool verbose = false;
+
+ // Whether or not to auto-version styles or layouts
+ // referencing attributes defined in a newer SDK
+ // level than the style or layout is defined for.
+ bool versionStylesAndLayouts = true;
};
-bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
- const Source& outputSource, std::queue<CompileItem>* queue) {
+
+bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) {
std::ifstream in(item.source.path, std::ifstream::binary);
if (!in) {
Logger::error(item.source) << strerror(errno) << std::endl;
return false;
}
+ BigBuffer outBuffer(1024);
+
+ // No resolver, since we are not compiling attributes here.
+ XmlFlattener flattener(table, {});
+
+ XmlFlattener::Options xmlOptions;
+ if (options.versionStylesAndLayouts) {
+ // We strip attributes that do not belong in this version of the resource.
+ // Non-version qualified resources have an implicit version 1 requirement.
+ xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
+ }
+
std::shared_ptr<BindingXmlPullParser> binding;
- std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+ std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
if (item.name.type == ResourceType::kLayout) {
- binding = std::make_shared<BindingXmlPullParser>(xmlParser);
- xmlParser = binding;
+ // Layouts may have defined bindings, so we need to make sure they get processed.
+ binding = std::make_shared<BindingXmlPullParser>(parser);
+ parser = binding;
}
- BigBuffer outBuffer(1024);
- XmlFlattener flattener(resolver);
-
- // We strip attributes that do not belong in this version of the resource.
- // Non-version qualified resources have an implicit version 1 requirement.
- XmlFlattener::Options options = { item.config.sdkVersion ? item.config.sdkVersion : 1 };
- Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, xmlParser, &outBuffer, options);
+ Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions);
if (!minStrippedSdk) {
return false;
}
@@ -376,24 +366,29 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
// with the version of the smallest SDK version stripped.
CompileItem newWork = item;
newWork.config.sdkVersion = minStrippedSdk.value();
- queue->push(newWork);
+ outQueue->push(newWork);
}
- std::ofstream out(outputSource.path, std::ofstream::binary);
- if (!out) {
- Logger::error(outputSource) << strerror(errno) << std::endl;
+ // Write the resulting compiled XML file to the output APK.
+ if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+ nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk."
+ << std::endl;
return false;
}
- if (!util::writeAll(out, outBuffer)) {
- Logger::error(outputSource) << strerror(errno) << std::endl;
- return false;
- }
+ if (binding && !options.bindingOutput.path.empty()) {
+ // We generated a binding xml file, write it out.
+ Source bindingOutput = options.bindingOutput;
+ appendPath(&bindingOutput.path, buildFileReference(item));
+
+ if (!mkdirs(bindingOutput.path)) {
+ Logger::error(bindingOutput) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ appendPath(&bindingOutput.path, "bind.xml");
- if (binding) {
- // We generated a binding xml file, write it out beside the output file.
- Source bindingOutput = outputSource;
- bindingOutput.path += ".bind.xml";
std::ofstream bout(bindingOutput.path);
if (!bout) {
Logger::error(bindingOutput) << strerror(errno) << std::endl;
@@ -408,100 +403,68 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
return true;
}
-bool compilePng(const Source& source, const Source& output) {
- std::ifstream in(source.path, std::ifstream::binary);
- if (!in) {
- Logger::error(source) << strerror(errno) << std::endl;
+bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
+ const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) {
+ std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
+ if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
return false;
}
- std::ofstream out(output.path, std::ofstream::binary);
- if (!out) {
- Logger::error(output) << strerror(errno) << std::endl;
+ std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
+
+ BigBuffer outBuffer(1024);
+ XmlFlattener flattener({}, resolver);
+ if (!flattener.flatten(item.source, xmlParser, &outBuffer, {})) {
return false;
}
- std::string err;
- Png png;
- if (!png.process(source, in, out, {}, &err)) {
- Logger::error(source) << err << std::endl;
+ if (outApk->add(outBuffer, item.apkPath.data(), ZipEntry::kCompressDeflated, nullptr) !=
+ android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write linked file '" << item.source
+ << "' to apk." << std::endl;
return false;
}
return true;
}
-bool copyFile(const Source& source, const Source& output) {
- std::ifstream in(source.path, std::ifstream::binary);
+bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
+ std::ifstream in(item.source.path, std::ifstream::binary);
if (!in) {
- Logger::error(source) << strerror(errno) << std::endl;
+ Logger::error(item.source) << strerror(errno) << std::endl;
return false;
}
- std::ofstream out(output.path, std::ofstream::binary);
- if (!out) {
- Logger::error(output) << strerror(errno) << std::endl;
+ BigBuffer outBuffer(4096);
+ std::string err;
+ Png png;
+ if (!png.process(item.source, in, &outBuffer, {}, &err)) {
+ Logger::error(item.source) << err << std::endl;
return false;
}
- if (out << in.rdbuf()) {
- Logger::error(output) << strerror(errno) << std::endl;
- return true;
+ if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+ nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write compiled '" << item.source
+ << "' to apk." << std::endl;
+ return false;
}
- return false;
+ return true;
}
-struct AaptOptions {
- enum class Phase {
- Full,
- Collect,
- Link,
- Compile,
- Manifest
- };
-
- // The phase to process.
- Phase phase;
-
- // Details about the app.
- AppInfo appInfo;
-
- // The location of the manifest file.
- Source manifest;
-
- // The source directories to walk and find resource files.
- std::vector<Source> sourceDirs;
-
- // The resource files to process and collect.
- std::vector<Source> collectFiles;
-
- // The binary table files to link.
- std::vector<Source> linkFiles;
-
- // The resource files to compile.
- std::vector<Source> compileFiles;
-
- // The libraries these files may reference.
- std::vector<Source> libraries;
-
- // Output path. This can be a directory or file
- // depending on the phase.
- Source output;
-
- // Directory to in which to generate R.java.
- Maybe<Source> generateJavaClass;
-
- // Whether to output verbose details about
- // compilation.
- bool verbose = false;
-};
-
-bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
- const AaptOptions& options) {
- Source outSource = options.output;
- appendPath(&outSource.path, "AndroidManifest.xml");
+bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
+ if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
+ ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
+ ZipFile* outApk) {
if (options.verbose) {
- Logger::note(outSource) << "compiling AndroidManifest.xml." << std::endl;
+ Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
}
std::ifstream in(options.manifest.path, std::ifstream::binary);
@@ -512,23 +475,16 @@ bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
BigBuffer outBuffer(1024);
std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
- XmlFlattener flattener(resolver);
+ XmlFlattener flattener({}, resolver);
- Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer,
- XmlFlattener::Options{});
- if (!result) {
+ if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, {})) {
return false;
}
- std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[outBuffer.size()]);
- uint8_t* p = data.get();
- for (const auto& b : outBuffer) {
- memcpy(p, b.buffer.get(), b.size);
- p += b.size;
- }
+ std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
android::ResXMLTree tree;
- if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
+ if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
return false;
}
@@ -537,14 +493,10 @@ bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
return false;
}
- std::ofstream out(outSource.path, std::ofstream::binary);
- if (!out) {
- Logger::error(outSource) << strerror(errno) << std::endl;
- return false;
- }
-
- if (!util::writeAll(out, outBuffer)) {
- Logger::error(outSource) << strerror(errno) << std::endl;
+ if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
+ ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
+ << std::endl;
return false;
}
return true;
@@ -562,10 +514,20 @@ bool loadAppInfo(const Source& source, AppInfo* outInfo) {
return parser.parse(source, pullParser, outInfo);
}
+static void printCommandsAndDie() {
+ std::cerr << "The following commands are supported:" << std::endl << std::endl;
+ std::cerr << "compile compiles a subset of resources" << std::endl;
+ std::cerr << "link links together compiled resources and libraries" << std::endl;
+ std::cerr << std::endl;
+ std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
+ << std::endl;
+ exit(1);
+}
+
static AaptOptions prepareArgs(int argc, char** argv) {
if (argc < 2) {
- std::cerr << "no command specified." << std::endl;
- exit(1);
+ std::cerr << "no command specified." << std::endl << std::endl;
+ printCommandsAndDie();
}
const StringPiece command(argv[1]);
@@ -574,32 +536,32 @@ static AaptOptions prepareArgs(int argc, char** argv) {
AaptOptions options;
- StringPiece outputDescription = "place output in file";
- if (command == "package") {
- options.phase = AaptOptions::Phase::Full;
- outputDescription = "place output in directory";
- } else if (command == "collect") {
- options.phase = AaptOptions::Phase::Collect;
+ if (command == "--version" || command == "version") {
+ std::cout << kAaptVersionStr << std::endl;
+ exit(0);
} else if (command == "link") {
options.phase = AaptOptions::Phase::Link;
} else if (command == "compile") {
options.phase = AaptOptions::Phase::Compile;
- outputDescription = "place output in directory";
- } else if (command == "manifest") {
- options.phase = AaptOptions::Phase::Manifest;
- outputDescription = "place AndroidManifest.xml in directory";
} else {
- std::cerr << "invalid command '" << command << "'." << std::endl;
- exit(1);
+ std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
+ printCommandsAndDie();
}
- if (options.phase == AaptOptions::Phase::Full) {
- flag::requiredFlag("-S", "add a directory in which to find resources",
+ if (options.phase == AaptOptions::Phase::Compile) {
+ flag::requiredFlag("--package", "Android package name",
+ [&options](const StringPiece& arg) {
+ options.appInfo.package = util::utf8ToUtf16(arg);
+ });
+ flag::optionalFlag("--binding", "Output directory for binding XML files",
[&options](const StringPiece& arg) {
- options.sourceDirs.push_back(Source{ arg.toString() });
+ options.bindingOutput = Source{ arg.toString() };
});
+ flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
+ false, &options.versionStylesAndLayouts);
- flag::requiredFlag("-M", "path to AndroidManifest.xml",
+ } else if (options.phase == AaptOptions::Phase::Link) {
+ flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
[&options](const StringPiece& arg) {
options.manifest = Source{ arg.toString() };
});
@@ -613,35 +575,16 @@ static AaptOptions prepareArgs(int argc, char** argv) {
[&options](const StringPiece& arg) {
options.generateJavaClass = Source{ arg.toString() };
});
-
- } else {
- if (options.phase != AaptOptions::Phase::Manifest) {
- flag::requiredFlag("--package", "Android package name",
- [&options](const StringPiece& arg) {
- options.appInfo.package = util::utf8ToUtf16(arg);
- });
- }
-
- if (options.phase != AaptOptions::Phase::Collect) {
- flag::optionalFlag("-I", "add an Android APK to link against",
- [&options](const StringPiece& arg) {
- options.libraries.push_back(Source{ arg.toString() });
- });
- }
-
- if (options.phase == AaptOptions::Phase::Link) {
- flag::optionalFlag("--java", "directory in which to generate R.java",
- [&options](const StringPiece& arg) {
- options.generateJavaClass = Source{ arg.toString() };
- });
- }
}
// Common flags for all steps.
- flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) {
+ flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
options.output = Source{ arg.toString() };
});
- flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
+
+ bool help = false;
+ flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
+ flag::optionalSwitch("-h", "displays this help menu", true, &help);
// Build the command string for output (eg. "aapt2 compile").
std::string fullCommand = "aapt2";
@@ -651,28 +594,18 @@ static AaptOptions prepareArgs(int argc, char** argv) {
// Actually read the command line flags.
flag::parse(argc, argv, fullCommand);
+ if (help) {
+ flag::usageAndDie(fullCommand);
+ }
+
// Copy all the remaining arguments.
- if (options.phase == AaptOptions::Phase::Collect) {
- for (const std::string& arg : flag::getArgs()) {
- options.collectFiles.push_back(Source{ arg });
- }
- } else if (options.phase == AaptOptions::Phase::Compile) {
- for (const std::string& arg : flag::getArgs()) {
- options.compileFiles.push_back(Source{ arg });
- }
- } else if (options.phase == AaptOptions::Phase::Link) {
- for (const std::string& arg : flag::getArgs()) {
- options.linkFiles.push_back(Source{ arg });
- }
- } else if (options.phase == AaptOptions::Phase::Manifest) {
- if (!flag::getArgs().empty()) {
- options.manifest = Source{ flag::getArgs()[0] };
- }
+ for (const std::string& arg : flag::getArgs()) {
+ options.input.push_back(Source{ arg });
}
return options;
}
-static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
+static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
const ConfigDescription& config) {
std::ifstream in(source.path, std::ifstream::binary);
if (!in) {
@@ -738,115 +671,91 @@ static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
};
}
-bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
- const std::shared_ptr<Resolver>& resolver) {
- const bool versionStyles = (options->phase == AaptOptions::Phase::Full ||
- options->phase == AaptOptions::Phase::Link);
- const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full ||
- options->phase == AaptOptions::Phase::Link);
- const bool compileFiles = (options->phase == AaptOptions::Phase::Full ||
- options->phase == AaptOptions::Phase::Compile);
- const bool flattenTable = (options->phase == AaptOptions::Phase::Full ||
- options->phase == AaptOptions::Phase::Collect ||
- options->phase == AaptOptions::Phase::Link);
- const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect;
-
- // Build the output table path.
- Source outputTable = options->output;
- if (options->phase == AaptOptions::Phase::Full) {
- appendPath(&outputTable.path, "resources.arsc");
- }
-
- bool error = false;
- std::queue<CompileItem> compileQueue;
+bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
+ if (table->begin() != table->end()) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(flattenerOptions);
+ if (!flattener.flatten(&buffer, *table)) {
+ Logger::error() << "failed to flatten resource table." << std::endl;
+ return false;
+ }
- // If source directories were specified, walk them looking for resource files.
- if (!options->sourceDirs.empty()) {
- const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
- FileFilter fileFilter;
- if (customIgnore && customIgnore[0]) {
- fileFilter.setPattern(customIgnore);
- } else {
- fileFilter.setPattern(
- "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
+ if (options.verbose) {
+ Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
+ << std::endl;
}
- for (const Source& source : options->sourceDirs) {
- if (!walkTree(source, fileFilter, &options->collectFiles)) {
- return false;
- }
+ if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
+ android::NO_ERROR) {
+ Logger::note(options.output) << "failed to store resource table." << std::endl;
+ return false;
}
}
+ return true;
+}
- // Load all binary resource tables.
- for (const Source& source : options->linkFiles) {
- error |= !loadBinaryResourceTable(table, source);
- }
+static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
+ ZipFile::kOpenReadWrite;
- if (error) {
- return false;
- }
+bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
+ const std::shared_ptr<Resolver>& resolver) {
+ std::map<std::shared_ptr<ResourceTable>, std::unique_ptr<ZipFile>> apkFiles;
+ std::unordered_set<std::u16string> linkedPackages;
- // Collect all the resource files.
- // Need to parse the resource type/config/filename.
- for (const Source& source : options->collectFiles) {
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
+ // Populate the linkedPackages with our own.
+ linkedPackages.insert(options.appInfo.package);
+
+ // Load all APK files.
+ for (const Source& source : options.input) {
+ std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
+ if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
+ Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
return false;
}
- const ResourcePathData& pathData = maybePathData.value();
- if (pathData.resourceDir == u"values") {
- if (options->verbose) {
- Logger::note(source) << "collecting values..." << std::endl;
- }
-
- error |= !collectValues(table, source, pathData.config);
- continue;
- }
+ std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
- << std::endl;
+ ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
+ if (!entry) {
+ Logger::error(source) << "missing 'resources.arsc'." << std::endl;
return false;
}
- ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+ void* uncompressedData = zipFile->uncompress(entry);
+ assert(uncompressedData);
- // Add the file name to the resource table.
- std::unique_ptr<FileReference> fileReference = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
- *type, pathData.config);
- error |= !table->addResource(resourceName, pathData.config, source.line(0),
- std::move(fileReference));
-
- if (pathData.extension == "xml") {
- error |= !collectXml(table, source, resourceName, pathData.config);
+ BinaryResourceParser parser(table, resolver, source, uncompressedData,
+ entry->getUncompressedLen());
+ if (!parser.parse()) {
+ free(uncompressedData);
+ return false;
}
+ free(uncompressedData);
- compileQueue.push(
- CompileItem{ source, resourceName, pathData.config, pathData.extension });
- }
+ // Keep track of where this table came from.
+ apkFiles[table] = std::move(zipFile);
- if (error) {
- return false;
+ // Add the package to the set of linked packages.
+ linkedPackages.insert(table->getPackage());
}
- // Version all styles referencing attributes outside of their specified SDK version.
- if (versionStyles) {
- versionStylesForCompat(table);
- }
+ for (auto& p : apkFiles) {
+ const std::shared_ptr<ResourceTable>& inTable = p.first;
- // Verify that all references are valid.
- Linker linker(table, resolver);
- if (!linker.linkAndValidate()) {
- return false;
+ if (!outTable->merge(std::move(*inTable))) {
+ return false;
+ }
}
- // Verify that all symbols exist.
- if (verifyNoMissingSymbols) {
+ {
+ // Now that everything is merged, let's link it.
+ Linker linker(outTable, resolver);
+ if (!linker.linkAndValidate()) {
+ return false;
+ }
+
+ // Verify that all symbols exist.
const auto& unresolvedRefs = linker.getUnresolvedReferences();
if (!unresolvedRefs.empty()) {
for (const auto& entry : unresolvedRefs) {
@@ -859,143 +768,192 @@ bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
}
}
- // Compile files.
- if (compileFiles) {
- // First process any input compile files.
- for (const Source& source : options->compileFiles) {
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
- return false;
- }
+ // Open the output APK file for writing.
+ ZipFile outApk;
+ if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
+ return false;
+ }
- const ResourcePathData& pathData = maybePathData.value();
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source) << "invalid resource type '" << pathData.resourceDir
- << "'." << std::endl;
- return false;
+ if (!compileManifest(options, resolver, &outApk)) {
+ return false;
+ }
+
+ for (auto& p : apkFiles) {
+ std::unique_ptr<ZipFile>& zipFile = p.second;
+
+ // TODO(adamlesinski): Get list of files to read when processing config filter.
+
+ const int numEntries = zipFile->getNumEntries();
+ for (int i = 0; i < numEntries; i++) {
+ ZipEntry* entry = zipFile->getEntryByIndex(i);
+ assert(entry);
+
+ StringPiece filename = entry->getFileName();
+ if (!util::stringStartsWith<char>(filename, "res/")) {
+ continue;
}
- ResourceName resourceName = { table->getPackage(), *type, pathData.name };
- compileQueue.push(
- CompileItem{ source, resourceName, pathData.config, pathData.extension });
- }
+ if (util::stringEndsWith<char>(filename, ".xml")) {
+ void* uncompressedData = zipFile->uncompress(entry);
+ assert(uncompressedData);
- // Now process the actual compile queue.
- for (; !compileQueue.empty(); compileQueue.pop()) {
- const CompileItem& item = compileQueue.front();
+ LinkItem item = { Source{ filename.toString() }, filename.toString() };
- // Create the output directory path from the resource type and config.
- std::stringstream outputPath;
- outputPath << item.name.type;
- if (item.config != ConfigDescription{}) {
- outputPath << "-" << item.config.toString();
+ if (!linkXml(options, resolver, item, uncompressedData,
+ entry->getUncompressedLen(), &outApk)) {
+ Logger::error(options.output) << "failed to link '" << filename << "'."
+ << std::endl;
+ return false;
+ }
+ } else {
+ if (outApk.add(zipFile.get(), entry, 0, nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to copy '" << filename << "'."
+ << std::endl;
+ return false;
+ }
}
+ }
+ }
- Source outSource = options->output;
- appendPath(&outSource.path, "res");
- appendPath(&outSource.path, outputPath.str());
+ // Generate the Java class file.
+ if (options.generateJavaClass) {
+ JavaClassGenerator generator(outTable, {});
- // Make the directory.
- if (!mkdirs(outSource.path)) {
- Logger::error(outSource) << strerror(errno) << std::endl;
- return false;
+ for (const std::u16string& package : linkedPackages) {
+ Source outPath = options.generateJavaClass.value();
+
+ // Build the output directory from the package name.
+ // Eg. com.android.app -> com/android/app
+ const std::string packageUtf8 = util::utf16ToUtf8(package);
+ for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
+ appendPath(&outPath.path, part);
}
- // Add the file name to the directory path.
- appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension);
+ if (!mkdirs(outPath.path)) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
+ }
- if (item.extension == "xml") {
- if (options->verbose) {
- Logger::note(outSource) << "compiling XML file." << std::endl;
- }
+ appendPath(&outPath.path, "R.java");
- error |= !compileXml(resolver, item, outSource, &compileQueue);
- } else if (item.extension == "png" || item.extension == "9.png") {
- if (options->verbose) {
- Logger::note(outSource) << "compiling png file." << std::endl;
- }
+ if (options.verbose) {
+ Logger::note(outPath) << "writing Java symbols." << std::endl;
+ }
- error |= !compilePng(item.source, outSource);
- } else {
- error |= !copyFile(item.source, outSource);
+ std::ofstream fout(outPath.path);
+ if (!fout) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
}
- }
- if (error) {
- return false;
+ if (!generator.generate(package, fout)) {
+ Logger::error(outPath) << generator.getError() << "." << std::endl;
+ return false;
+ }
}
}
- // Compile and validate the AndroidManifest.xml.
- if (!options->manifest.path.empty()) {
- if (!compileAndroidManifest(resolver, *options)) {
- return false;
- }
+ // Flatten the resource table.
+ TableFlattener::Options flattenerOptions;
+ flattenerOptions.useExtendedChunks = false;
+ if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
+ return false;
}
- // Generate the Java class file.
- if (options->generateJavaClass) {
- Source outPath = options->generateJavaClass.value();
- if (options->verbose) {
- Logger::note() << "writing symbols to " << outPath << "." << std::endl;
- }
+ outApk.flush();
+ return true;
+}
- // Build the output directory from the package name.
- // Eg. com.android.app -> com/android/app
- const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage());
- for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
- appendPath(&outPath.path, part);
- }
+bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver) {
+ std::queue<CompileItem> compileQueue;
+ bool error = false;
- if (!mkdirs(outPath.path)) {
- Logger::error(outPath) << strerror(errno) << std::endl;
+ // Compile all the resource files passed in on the command line.
+ for (const Source& source : options.input) {
+ // Need to parse the resource type/config/filename.
+ Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+ if (!maybePathData) {
return false;
}
- appendPath(&outPath.path, "R.java");
+ const ResourcePathData& pathData = maybePathData.value();
+ if (pathData.resourceDir == u"values") {
+ // The file is in the values directory, which means its contents will
+ // go into the resource table.
+ if (options.verbose) {
+ Logger::note(source) << "compiling values." << std::endl;
+ }
- std::ofstream fout(outPath.path);
- if (!fout) {
- Logger::error(outPath) << strerror(errno) << std::endl;
- return false;
- }
+ error |= !compileValues(table, source, pathData.config);
+ } else {
+ // The file is in a directory like 'layout' or 'drawable'. Find out
+ // the type.
+ const ResourceType* type = parseResourceType(pathData.resourceDir);
+ if (!type) {
+ Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
+ << std::endl;
+ return false;
+ }
- JavaClassGenerator generator(table, {});
- if (!generator.generate(fout)) {
- Logger::error(outPath) << generator.getError() << "." << std::endl;
- return false;
+ compileQueue.push(CompileItem{
+ source,
+ ResourceName{ table->getPackage(), *type, pathData.name },
+ pathData.config,
+ pathData.extension
+ });
}
}
- // Flatten the resource table.
- if (flattenTable && table->begin() != table->end()) {
- BigBuffer buffer(1024);
- TableFlattener::Options tableOptions;
- tableOptions.useExtendedChunks = useExtendedChunks;
- TableFlattener flattener(tableOptions);
- if (!flattener.flatten(&buffer, *table)) {
- Logger::error() << "failed to flatten resource table." << std::endl;
- return false;
- }
+ if (error) {
+ return false;
+ }
- if (options->verbose) {
- Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
- << std::endl;
- }
+ // Version all styles referencing attributes outside of their specified SDK version.
+ if (options.versionStylesAndLayouts) {
+ versionStylesForCompat(table);
+ }
- std::ofstream fout(outputTable.path, std::ofstream::binary);
- if (!fout) {
- Logger::error(outputTable) << strerror(errno) << "." << std::endl;
- return false;
- }
+ // Open the output APK file for writing.
+ ZipFile outApk;
+ if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
+ return false;
+ }
- if (!util::writeAll(fout, buffer)) {
- Logger::error(outputTable) << strerror(errno) << "." << std::endl;
- return false;
+ // Compile each file.
+ for (; !compileQueue.empty(); compileQueue.pop()) {
+ const CompileItem& item = compileQueue.front();
+
+ // Add the file name to the resource table.
+ error |= !addFileReference(table, item);
+
+ if (item.extension == "xml") {
+ error |= !compileXml(options, table, item, &compileQueue, &outApk);
+ } else if (item.extension == "png" || item.extension == "9.png") {
+ error |= !compilePng(options, item, &outApk);
+ } else {
+ error |= !copyFile(options, item, &outApk);
}
- fout.flush();
}
+
+ if (error) {
+ return false;
+ }
+
+ // Link and assign resource IDs.
+ Linker linker(table, resolver);
+ if (!linker.linkAndValidate()) {
+ return false;
+ }
+
+ // Flatten the resource table.
+ if (!writeResourceTable(options, table, {}, &outApk)) {
+ return false;
+ }
+
+ outApk.flush();
return true;
}
@@ -1057,10 +1015,16 @@ int main(int argc, char** argv) {
// Make the resolver that will cache IDs for us.
std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
- // Do the work.
- if (!doAll(&options, table, resolver)) {
- Logger::error() << "aapt exiting with failures." << std::endl;
- return 1;
+ if (options.phase == AaptOptions::Phase::Compile) {
+ if (!compile(options, table, resolver)) {
+ Logger::error() << "aapt exiting with failures." << std::endl;
+ return 1;
+ }
+ } else if (options.phase == AaptOptions::Phase::Link) {
+ if (!link(options, table, resolver)) {
+ Logger::error() << "aapt exiting with failures." << std::endl;
+ return 1;
+ }
}
return 0;
}
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
new file mode 100644
index 000000000000..1e15e2071e65
--- /dev/null
+++ b/tools/aapt2/NameMangler.h
@@ -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.
+ */
+
+#ifndef AAPT_NAME_MANGLER_H
+#define AAPT_NAME_MANGLER_H
+
+#include <string>
+
+namespace aapt {
+
+struct NameMangler {
+ /**
+ * Mangles the name in `outName` with the `package` and stores the mangled
+ * result in `outName`. The mangled name should contain symbols that are
+ * illegal to define in XML, so that there will never be name mangling
+ * collisions.
+ */
+ static void mangle(const std::u16string& package, std::u16string* outName) {
+ *outName = package + u"$" + *outName;
+ }
+
+ /**
+ * Unmangles the name in `outName`, storing the correct name back in `outName`
+ * and the package in `outPackage`. Returns true if the name was unmangled or
+ * false if the name was never mangled to begin with.
+ */
+ static bool unmangle(std::u16string* outName, std::u16string* outPackage) {
+ size_t pivot = outName->find(u'$');
+ if (pivot == std::string::npos) {
+ return false;
+ }
+
+ outPackage->assign(outName->data(), pivot);
+ outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1));
+ return true;
+ }
+};
+
+} // namespace aapt
+
+#endif // AAPT_NAME_MANGLER_H
diff --git a/tools/aapt2/NameMangler_test.cpp b/tools/aapt2/NameMangler_test.cpp
new file mode 100644
index 000000000000..6103655c15e0
--- /dev/null
+++ b/tools/aapt2/NameMangler_test.cpp
@@ -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.
+ */
+
+#include "NameMangler.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+TEST(NameManglerTest, MangleName) {
+ std::u16string package = u"android.appcompat";
+ std::u16string name = u"Platform.AppCompat";
+
+ NameMangler::mangle(package, &name);
+ EXPECT_EQ(name, u"android.appcompat$Platform.AppCompat");
+
+ std::u16string newPackage;
+ ASSERT_TRUE(NameMangler::unmangle(&name, &newPackage));
+ EXPECT_EQ(name, u"Platform.AppCompat");
+ EXPECT_EQ(newPackage, u"android.appcompat");
+}
+
+TEST(NameManglerTest, IgnoreUnmangledName) {
+ std::u16string package;
+ std::u16string name = u"foo_bar";
+
+ EXPECT_FALSE(NameMangler::unmangle(&name, &package));
+ EXPECT_EQ(name, u"foo_bar");
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp
index 76120ac24601..4e9b68e8c95f 100644
--- a/tools/aapt2/Png.cpp
+++ b/tools/aapt2/Png.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "BigBuffer.h"
#include "Logger.h"
#include "Png.h"
#include "Source.h"
@@ -85,17 +86,12 @@ static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t l
}
static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
- std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
- if (!output->write(reinterpret_cast<const char*>(data), length)) {
- png_error(writePtr, strerror(errno));
- }
+ BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
+ png_bytep buf = outBuffer->nextBlock<png_byte>(length);
+ memcpy(buf, data, length);
}
-static void flushDataToStream(png_structp writePtr) {
- std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
- if (!output->flush()) {
- png_error(writePtr, strerror(errno));
- }
+static void flushDataToStream(png_structp /*writePtr*/) {
}
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
@@ -1196,7 +1192,7 @@ getout:
}
-bool Png::process(const Source& source, std::istream& input, std::ostream& output,
+bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer,
const Options& options, std::string* outError) {
png_byte signature[kPngSignatureSize];
@@ -1262,7 +1258,7 @@ bool Png::process(const Source& source, std::istream& input, std::ostream& outpu
png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
// Set the write function to write to std::ostream.
- png_set_write_fn(writePtr, (png_voidp)&output, writeDataToStream, flushDataToStream);
+ png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
outError)) {
diff --git a/tools/aapt2/Png.h b/tools/aapt2/Png.h
index bc8075424abc..4577ab89d2d9 100644
--- a/tools/aapt2/Png.h
+++ b/tools/aapt2/Png.h
@@ -17,6 +17,7 @@
#ifndef AAPT_PNG_H
#define AAPT_PNG_H
+#include "BigBuffer.h"
#include "Source.h"
#include <iostream>
@@ -29,7 +30,7 @@ struct Png {
int grayScaleTolerance = 0;
};
- bool process(const Source& source, std::istream& input, std::ostream& output,
+ bool process(const Source& source, std::istream& input, BigBuffer* outBuffer,
const Options& options, std::string* outError);
};
diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/ResChunkPullParser.h
index 7366c89968df..1426ed23a5c7 100644
--- a/tools/aapt2/ResChunkPullParser.h
+++ b/tools/aapt2/ResChunkPullParser.h
@@ -74,6 +74,22 @@ private:
std::string mLastError;
};
+template <typename T>
+inline static const T* convertTo(const android::ResChunk_header* chunk) {
+ if (chunk->headerSize < sizeof(T)) {
+ return nullptr;
+ }
+ return reinterpret_cast<const T*>(chunk);
+}
+
+inline static const uint8_t* getChunkData(const android::ResChunk_header& chunk) {
+ return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
+}
+
+inline static size_t getChunkDataLen(const android::ResChunk_header& chunk) {
+ return chunk.size - chunk.headerSize;
+}
+
//
// Implementation
//
diff --git a/tools/aapt2/Resolver.cpp b/tools/aapt2/Resolver.cpp
index 93b5e98b1359..ae006abba66d 100644
--- a/tools/aapt2/Resolver.cpp
+++ b/tools/aapt2/Resolver.cpp
@@ -15,6 +15,7 @@
*/
#include "Maybe.h"
+#include "NameMangler.h"
#include "Resolver.h"
#include "Resource.h"
#include "ResourceTable.h"
@@ -31,6 +32,12 @@ namespace aapt {
Resolver::Resolver(std::shared_ptr<const ResourceTable> table,
std::shared_ptr<const android::AssetManager> sources) :
mTable(table), mSources(sources) {
+ const android::ResTable& resTable = mSources->getResources(false);
+ const size_t packageCount = resTable.getBasePackageCount();
+ for (size_t i = 0; i < packageCount; i++) {
+ std::u16string packageName = resTable.getBasePackageName(i).string();
+ mIncludedPackages.insert(std::move(packageName));
+ }
}
Maybe<ResourceId> Resolver::findId(const ResourceName& name) {
@@ -47,9 +54,31 @@ Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) {
return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
}
+ ResourceName mangledName;
+ const ResourceName* nameToSearch = &name;
+ if (name.package != mTable->getPackage()) {
+ // This may be a reference to an included resource or
+ // to a mangled resource.
+ if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) {
+ // This is not in our included set, so mangle the name and
+ // check for that.
+ mangledName.entry = name.entry;
+ NameMangler::mangle(name.package, &mangledName.entry);
+ mangledName.package = mTable->getPackage();
+ mangledName.type = name.type;
+ nameToSearch = &mangledName;
+ } else {
+ const CacheEntry* cacheEntry = buildCacheEntry(name);
+ if (cacheEntry) {
+ return Entry{ cacheEntry->id, cacheEntry->attr.get() };
+ }
+ return {};
+ }
+ }
+
const ResourceTableType* type;
const ResourceEntry* entry;
- std::tie(type, entry) = mTable->findResource(name);
+ std::tie(type, entry) = mTable->findResource(*nameToSearch);
if (type && entry) {
Entry result = {};
if (mTable->getPackageId() != ResourceTable::kUnsetPackageId &&
@@ -65,11 +94,6 @@ Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) {
}
return result;
}
-
- const CacheEntry* cacheEntry = buildCacheEntry(name);
- if (cacheEntry) {
- return Entry{ cacheEntry->id, cacheEntry->attr.get() };
- }
return {};
}
diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h
index 90a8cd975d73..cb2234d860c1 100644
--- a/tools/aapt2/Resolver.h
+++ b/tools/aapt2/Resolver.h
@@ -26,6 +26,7 @@
#include <androidfw/ResourceTypes.h>
#include <memory>
#include <vector>
+#include <unordered_set>
namespace aapt {
@@ -94,6 +95,7 @@ private:
std::shared_ptr<const ResourceTable> mTable;
std::shared_ptr<const android::AssetManager> mSources;
std::map<ResourceName, CacheEntry> mCache;
+ std::unordered_set<std::u16string> mIncludedPackages;
};
inline const std::u16string& Resolver::getDefaultPackage() const {
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 4d2c64c0a375..f928acd7c8d7 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -193,8 +193,7 @@ inline ::std::ostream& operator<<(::std::ostream& out,
// ResourceType implementation.
//
-inline ::std::ostream& operator<<(::std::ostream& out,
- const ResourceType& val) {
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) {
return out << toString(val);
}
@@ -221,6 +220,14 @@ inline bool ResourceName::operator!=(const ResourceName& rhs) const {
!= std::tie(rhs.package, rhs.type, rhs.entry);
}
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
+ if (!name.package.empty()) {
+ out << name.package << ":";
+ }
+ return out << name.type << "/" << name.entry;
+}
+
+
//
// ResourceNameRef implementation.
//
@@ -264,8 +271,7 @@ inline bool ResourceNameRef::operator!=(const ResourceNameRef& rhs) const {
!= std::tie(rhs.package, rhs.type, rhs.entry);
}
-inline ::std::ostream& operator<<(::std::ostream& out,
- const ResourceNameRef& name) {
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) {
if (!name.package.empty()) {
out << name.package << ":";
}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 4c9618725720..943892dff024 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -22,6 +22,8 @@
#include "Util.h"
#include "XliffXmlPullParser.h"
+#include <sstream>
+
namespace aapt {
void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
@@ -107,6 +109,71 @@ bool ResourceParser::tryParseAttributeReference(const StringPiece16& str,
return false;
}
+/*
+ * Style parent's are a bit different. We accept the following formats:
+ *
+ * @[package:]style/<entry>
+ * ?[package:]style/<entry>
+ * <package>:[style/]<entry>
+ * [package:style/]<entry>
+ */
+bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference,
+ std::string* outError) {
+ if (str.empty()) {
+ return true;
+ }
+
+ StringPiece16 name = str;
+
+ bool hasLeadingIdentifiers = false;
+ bool privateRef = false;
+
+ // Skip over these identifiers. A style's parent is a normal reference.
+ if (name.data()[0] == u'@' || name.data()[0] == u'?') {
+ hasLeadingIdentifiers = true;
+ name = name.substr(1, name.size() - 1);
+ if (name.data()[0] == u'*') {
+ privateRef = true;
+ name = name.substr(1, name.size() - 1);
+ }
+ }
+
+ ResourceNameRef ref;
+ ref.type = ResourceType::kStyle;
+
+ StringPiece16 typeStr;
+ extractResourceName(name, &ref.package, &typeStr, &ref.entry);
+ if (!typeStr.empty()) {
+ // If we have a type, make sure it is a Style.
+ const ResourceType* parsedType = parseResourceType(typeStr);
+ if (!parsedType || *parsedType != ResourceType::kStyle) {
+ std::stringstream err;
+ err << "invalid resource type '" << typeStr << "' for parent of style";
+ *outError = err.str();
+ return false;
+ }
+ } else {
+ // No type was defined, this should not have a leading identifier.
+ if (hasLeadingIdentifiers) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *outError = err.str();
+ return false;
+ }
+ }
+
+ if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *outError = err.str();
+ return false;
+ }
+
+ outReference->name = ref.toResourceName();
+ outReference->privateReference = privateRef;
+ return true;
+}
+
std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str,
const StringPiece16& defaultPackage,
bool* outCreate) {
@@ -885,15 +952,16 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) {
bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
- std::unique_ptr<Attribute> attr = parseAttrImpl(parser, resourceName, false);
+ ResourceName actualName = resourceName.toResourceName();
+ std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false);
if (!attr) {
return false;
}
- return mTable->addResource(resourceName, mConfig, source, std::move(attr));
+ return mTable->addResource(actualName, mConfig, source, std::move(attr));
}
std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
- const ResourceNameRef& resourceName,
+ ResourceName* resourceName,
bool weak) {
uint32_t typeMask = 0;
@@ -911,6 +979,18 @@ std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
}
}
+ // If this is a declaration, the package name may be in the name. Separate these out.
+ // Eg. <attr name="android:text" />
+ // No format attribute is allowed.
+ if (weak && formatAttrIter == endAttrIter) {
+ StringPiece16 package, type, name;
+ extractResourceName(resourceName->entry, &package, &type, &name);
+ if (type.empty() && !package.empty()) {
+ resourceName->package = package.toString();
+ resourceName->entry = name.toString();
+ }
+ }
+
std::vector<Attribute::Symbol> items;
bool error = false;
@@ -1079,31 +1159,15 @@ bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) {
bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
const auto endAttrIter = parser->endAttributes();
const auto parentAttrIter = parser->findAttribute(u"", u"parent");
if (parentAttrIter != endAttrIter) {
- ResourceNameRef ref;
- bool create = false;
- bool privateRef = false;
- if (tryParseReference(parentAttrIter->value, &ref, &create, &privateRef)) {
- if (create) {
- mLogger.error(source.line)
- << "parent of style can not be an ID."
- << std::endl;
- return false;
- }
- style->parent.name = ref.toResourceName();
- style->parent.privateReference = privateRef;
- } else if (tryParseAttributeReference(parentAttrIter->value, &ref)) {
- style->parent.name = ref.toResourceName();
- } else {
- // TODO(adamlesinski): Try parsing without the '@' or '?'.
- // Also, make sure to check the entry name for weird symbols.
- style->parent.name = ResourceName {
- {}, ResourceType::kStyle, parentAttrIter->value
- };
+ std::string errStr;
+ if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) {
+ mLogger.error(source.line) << errStr << "." << std::endl;
+ return false;
}
if (style->parent.name.package.empty()) {
@@ -1277,15 +1341,13 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
}
// Copy because our iterator will be invalidated.
- std::u16string attrName = attrIter->value;
-
- ResourceNameRef attrResourceName = {
+ ResourceName attrResourceName = {
mTable->getPackage(),
ResourceType::kAttr,
- attrName
+ attrIter->value
};
- std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, attrResourceName, true);
+ std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true);
if (!attr) {
success = false;
continue;
@@ -1293,9 +1355,13 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
styleable->entries.emplace_back(attrResourceName);
- success &= mTable->addResource(attrResourceName, mConfig,
- mSource.line(childParser.getLineNumber()),
- std::move(attr));
+ // The package may have been corrected to another package. If that is so,
+ // we don't add the declaration.
+ if (attrResourceName.package == mTable->getPackage()) {
+ success &= mTable->addResource(attrResourceName, mConfig,
+ mSource.line(childParser.getLineNumber()),
+ std::move(attr));
+ }
} else if (elementName != u"eat-comment" && elementName != u"skip") {
mLogger.error(childParser.getLineNumber())
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 96bba4febf99..52194bd02f99 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -64,6 +64,17 @@ public:
ResourceNameRef* outReference);
/*
+ * Returns true if the string `str` was parsed as a valid reference to a style.
+ * The format for a style parent is slightly more flexible than a normal reference:
+ *
+ * @[package:]style/<entry> or
+ * ?[package:]style/<entry> or
+ * <package>:[style/]<entry>
+ */
+ static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference,
+ std::string* outError);
+
+ /*
* Returns a Reference object if the string was parsed as a resource or attribute reference,
* ( @[+][package:]type/name | ?[package:]type/name )
* assigning defaultPackage if the package was not present in the string, and setting
@@ -166,7 +177,7 @@ private:
bool parsePublic(XmlPullParser* parser, const StringPiece16& name);
bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName);
std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
- const ResourceNameRef& resourceName,
+ ResourceName* resourceName,
bool weak);
bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
Attribute::Symbol* outSymbol);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 5afbaf4618e0..63352decbb11 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -94,6 +94,31 @@ TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) {
&privateRef));
}
+TEST(ResourceParserReferenceTest, ParseStyleParentReference) {
+ Reference ref;
+ std::string errStr;
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+}
+
struct ResourceParserTest : public ::testing::Test {
virtual void SetUp() override {
mTable = std::make_shared<ResourceTable>();
@@ -283,7 +308,7 @@ TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
TEST_F(ResourceParserTest, ParseStyle) {
std::stringstream input;
- input << "<style name=\"foo\" parent=\"fu\">" << std::endl
+ input << "<style name=\"foo\" parent=\"@style/fu\">" << std::endl
<< " <item name=\"bar\">#ffffffff</item>" << std::endl
<< " <item name=\"bat\">@string/hey</item>" << std::endl
<< " <item name=\"baz\"><b>hey</b></item>" << std::endl
@@ -304,6 +329,17 @@ TEST_F(ResourceParserTest, ParseStyle) {
(ResourceName{ u"android", ResourceType::kAttr, u"baz" }));
}
+TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
+ std::stringstream input;
+ input << "<style name=\"foo\" parent=\"com.app:Theme\"/>" << std::endl;
+ ASSERT_TRUE(testParse(input));
+
+ const Style* style = findResource<Style>(
+ ResourceName{ u"android", ResourceType::kStyle, u"foo" });
+ ASSERT_NE(style, nullptr);
+ EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name);
+}
+
TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
std::stringstream input;
input << "<string name=\"foo\">@+id/bar</string>" << std::endl;
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 794090d0b74c..02be6516f570 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -16,6 +16,7 @@
#include "ConfigDescription.h"
#include "Logger.h"
+#include "NameMangler.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "Util.h"
@@ -311,6 +312,71 @@ bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId res
return true;
}
+bool ResourceTable::merge(ResourceTable&& other) {
+ const bool mangleNames = mPackage != other.getPackage();
+ std::u16string mangledName;
+
+ for (auto& otherType : other) {
+ std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type);
+ if (type->publicStatus.isPublic && otherType->publicStatus.isPublic &&
+ type->typeId != otherType->typeId) {
+ Logger::error() << "can not merge type '" << type->type << "': conflicting public IDs "
+ << "(" << type->typeId << " vs " << otherType->typeId << ")."
+ << std::endl;
+ return false;
+ }
+
+ for (auto& otherEntry : otherType->entries) {
+ const std::u16string* nameToAdd = &otherEntry->name;
+ if (mangleNames) {
+ mangledName = otherEntry->name;
+ NameMangler::mangle(other.getPackage(), &mangledName);
+ nameToAdd = &mangledName;
+ }
+
+ std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd);
+ if (entry->publicStatus.isPublic && otherEntry->publicStatus.isPublic &&
+ entry->entryId != otherEntry->entryId) {
+ Logger::error() << "can not merge entry '" << type->type << "/" << entry->name
+ << "': conflicting public IDs "
+ << "(" << entry->entryId << " vs " << entry->entryId << ")."
+ << std::endl;
+ return false;
+ }
+
+ for (ResourceConfigValue& otherValue : otherEntry->values) {
+ auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
+ otherValue.config, compareConfigs);
+ if (iter != entry->values.end() && iter->config == otherValue.config) {
+ int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value);
+ if (collisionResult > 0) {
+ // Take the incoming value.
+ iter->source = std::move(otherValue.source);
+ iter->comment = std::move(otherValue.comment);
+ iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool));
+ } else if (collisionResult == 0) {
+ ResourceNameRef resourceName = { mPackage, type->type, entry->name };
+ Logger::error(otherValue.source)
+ << "resource '" << resourceName << "' has a conflicting value for "
+ << "configuration (" << otherValue.config << ")."
+ << std::endl;
+ Logger::note(iter->source) << "originally defined here." << std::endl;
+ return false;
+ }
+ } else {
+ entry->values.insert(iter, ResourceConfigValue{
+ otherValue.config,
+ std::move(otherValue.source),
+ std::move(otherValue.comment),
+ std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)),
+ });
+ }
+ }
+ }
+ }
+ return true;
+}
+
std::tuple<const ResourceTableType*, const ResourceEntry*>
ResourceTable::findResource(const ResourceNameRef& name) const {
if (name.package != mPackage) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 57b52131958e..3591d11b2282 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -148,6 +148,12 @@ public:
bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source);
+ /*
+ * Merges the resources from `other` into this table, mangling the names of the resources
+ * if `other` has a different package name.
+ */
+ bool merge(ResourceTable&& other);
+
/**
* Returns the string pool used by this ResourceTable.
* Values that reference strings should use this pool to create
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 785ea1546580..06d8699730de 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -31,7 +31,7 @@ struct TestValue : public Value {
TestValue(StringPiece16 str) : value(str.toString()) {
}
- TestValue* clone() const override {
+ TestValue* clone(StringPool* /*newPool*/) const override {
return new TestValue(value);
}
@@ -48,7 +48,7 @@ struct TestWeakValue : public Value {
return true;
}
- TestWeakValue* clone() const override {
+ TestWeakValue* clone(StringPool* /*newPool*/) const override {
return new TestWeakValue();
}
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 60ef1a8e4d0a..3a6d65d1d96d 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -39,8 +39,8 @@ bool Item::isItem() const {
RawString::RawString(const StringPool::Ref& ref) : value(ref) {
}
-RawString* RawString::clone() const {
- return new RawString(value);
+RawString* RawString::clone(StringPool* newPool) const {
+ return new RawString(newPool->makeRef(*value));
}
bool RawString::flatten(android::Res_value& outValue) const {
@@ -71,7 +71,7 @@ bool Reference::flatten(android::Res_value& outValue) const {
return true;
}
-Reference* Reference::clone() const {
+Reference* Reference::clone(StringPool* /*newPool*/) const {
Reference* ref = new Reference();
ref->referenceType = referenceType;
ref->name = name;
@@ -106,7 +106,7 @@ bool Id::flatten(android::Res_value& out) const {
return true;
}
-Id* Id::clone() const {
+Id* Id::clone(StringPool* /*newPool*/) const {
return new Id();
}
@@ -128,8 +128,8 @@ bool String::flatten(android::Res_value& outValue) const {
return true;
}
-String* String::clone() const {
- return new String(value);
+String* String::clone(StringPool* newPool) const {
+ return new String(newPool->makeRef(*value));
}
void String::print(std::ostream& out) const {
@@ -149,8 +149,8 @@ bool StyledString::flatten(android::Res_value& outValue) const {
return true;
}
-StyledString* StyledString::clone() const {
- return new StyledString(value);
+StyledString* StyledString::clone(StringPool* newPool) const {
+ return new StyledString(newPool->makeRef(value));
}
void StyledString::print(std::ostream& out) const {
@@ -170,8 +170,8 @@ bool FileReference::flatten(android::Res_value& outValue) const {
return true;
}
-FileReference* FileReference::clone() const {
- return new FileReference(path);
+FileReference* FileReference::clone(StringPool* newPool) const {
+ return new FileReference(newPool->makeRef(*path));
}
void FileReference::print(std::ostream& out) const {
@@ -186,7 +186,7 @@ bool BinaryPrimitive::flatten(android::Res_value& outValue) const {
return true;
}
-BinaryPrimitive* BinaryPrimitive::clone() const {
+BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
return new BinaryPrimitive(value);
}
@@ -227,7 +227,7 @@ bool Sentinel::flatten(android::Res_value& outValue) const {
return true;
}
-Sentinel* Sentinel::clone() const {
+Sentinel* Sentinel::clone(StringPool* /*newPool*/) const {
return new Sentinel();
}
@@ -243,7 +243,7 @@ bool Attribute::isWeak() const {
return weak;
}
-Attribute* Attribute::clone() const {
+Attribute* Attribute::clone(StringPool* /*newPool*/) const {
Attribute* attr = new Attribute(weak);
attr->typeMask = typeMask;
std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
@@ -371,13 +371,20 @@ static ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol&
return out << s.symbol.name.entry << "=" << s.value;
}
-Style* Style::clone() const {
- Style* style = new Style();
+Style::Style(bool weak) : weak(weak) {
+}
+
+bool Style::isWeak() const {
+ return weak;
+}
+
+Style* Style::clone(StringPool* newPool) const {
+ Style* style = new Style(weak);
style->parent = parent;
for (auto& entry : entries) {
style->entries.push_back(Entry{
entry.key,
- std::unique_ptr<Item>(entry.value->clone())
+ std::unique_ptr<Item>(entry.value->clone(newPool))
});
}
return style;
@@ -399,10 +406,10 @@ static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value
return out;
}
-Array* Array::clone() const {
+Array* Array::clone(StringPool* newPool) const {
Array* array = new Array();
for (auto& item : items) {
- array->items.emplace_back(std::unique_ptr<Item>(item->clone()));
+ array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool)));
}
return array;
}
@@ -413,12 +420,12 @@ void Array::print(std::ostream& out) const {
<< "]";
}
-Plural* Plural::clone() const {
+Plural* Plural::clone(StringPool* newPool) const {
Plural* p = new Plural();
const size_t count = values.size();
for (size_t i = 0; i < count; i++) {
if (values[i]) {
- p->values[i] = std::unique_ptr<Item>(values[i]->clone());
+ p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool));
}
}
return p;
@@ -432,7 +439,7 @@ static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Ite
return out << *item;
}
-Styleable* Styleable::clone() const {
+Styleable* Styleable::clone(StringPool* /*newPool*/) const {
Styleable* styleable = new Styleable();
std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
return styleable;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index f25bcf08b330..e3352f3ee612 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -63,7 +63,7 @@ struct Value {
/**
* Clone the value.
*/
- virtual Value* clone() const = 0;
+ virtual Value* clone(StringPool* newPool) const = 0;
/**
* Human readable printout of this value.
@@ -92,7 +92,7 @@ struct Item : public Value {
/**
* Clone the Item.
*/
- virtual Item* clone() const override = 0;
+ virtual Item* clone(StringPool* newPool) const override = 0;
/**
* Fills in an android::Res_value structure with this Item's binary representation.
@@ -132,7 +132,7 @@ struct Reference : public BaseItem<Reference> {
Reference(const ResourceId& i, Type type = Type::kResource);
bool flatten(android::Res_value& outValue) const override;
- Reference* clone() const override;
+ Reference* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -142,7 +142,7 @@ struct Reference : public BaseItem<Reference> {
struct Id : public BaseItem<Id> {
bool isWeak() const override;
bool flatten(android::Res_value& out) const override;
- Id* clone() const override;
+ Id* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -157,7 +157,7 @@ struct RawString : public BaseItem<RawString> {
RawString(const StringPool::Ref& ref);
bool flatten(android::Res_value& outValue) const override;
- RawString* clone() const override;
+ RawString* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -167,7 +167,7 @@ struct String : public BaseItem<String> {
String(const StringPool::Ref& ref);
bool flatten(android::Res_value& outValue) const override;
- String* clone() const override;
+ String* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -177,7 +177,7 @@ struct StyledString : public BaseItem<StyledString> {
StyledString(const StringPool::StyleRef& ref);
bool flatten(android::Res_value& outValue) const override;
- StyledString* clone() const override;
+ StyledString* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -188,7 +188,7 @@ struct FileReference : public BaseItem<FileReference> {
FileReference(const StringPool::Ref& path);
bool flatten(android::Res_value& outValue) const override;
- FileReference* clone() const override;
+ FileReference* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -202,8 +202,8 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
BinaryPrimitive(const android::Res_value& val);
bool flatten(android::Res_value& outValue) const override;
- BinaryPrimitive* clone() const override;
- void print(::std::ostream& out) const override;
+ BinaryPrimitive* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
};
/**
@@ -214,8 +214,8 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
struct Sentinel : public BaseItem<Sentinel> {
bool isWeak() const override;
bool flatten(android::Res_value& outValue) const override;
- Sentinel* clone() const override;
- void print(::std::ostream& out) const override;
+ Sentinel* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
};
struct Attribute : public BaseValue<Attribute> {
@@ -233,7 +233,7 @@ struct Attribute : public BaseValue<Attribute> {
Attribute(bool w, uint32_t t = 0u);
bool isWeak() const override;
- virtual Attribute* clone() const override;
+ virtual Attribute* clone(StringPool* newPool) const override;
virtual void print(std::ostream& out) const override;
};
@@ -243,17 +243,20 @@ struct Style : public BaseValue<Style> {
std::unique_ptr<Item> value;
};
+ bool weak;
Reference parent;
std::vector<Entry> entries;
- Style* clone() const override;
+ Style(bool weak);
+ bool isWeak() const override;
+ Style* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
struct Array : public BaseValue<Array> {
std::vector<std::unique_ptr<Item>> items;
- Array* clone() const override;
+ Array* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -270,14 +273,14 @@ struct Plural : public BaseValue<Plural> {
std::array<std::unique_ptr<Item>, Count> values;
- Plural* clone() const override;
+ Plural* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
struct Styleable : public BaseValue<Styleable> {
std::vector<Reference> entries;
- Styleable* clone() const override;
+ Styleable* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index b159a0076b02..b983a53f4273 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -175,6 +175,25 @@ StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context&
return StyleRef(styleEntry);
}
+StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) {
+ Entry* entry = new Entry();
+ entry->value = *ref.mEntry->str;
+ entry->context = ref.mEntry->str.mEntry->context;
+ entry->index = mStrings.size();
+ entry->ref = 0;
+ mStrings.emplace_back(entry);
+ mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry));
+
+ StyleEntry* styleEntry = new StyleEntry();
+ styleEntry->str = Ref(entry);
+ for (const Span& span : ref.mEntry->spans) {
+ styleEntry->spans.emplace_back(Span{ makeRef(*span.name), span.firstChar, span.lastChar });
+ }
+ styleEntry->ref = 0;
+ mStyles.emplace_back(styleEntry);
+ return StyleRef(styleEntry);
+}
+
void StringPool::merge(StringPool&& pool) {
mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end());
pool.mIndexedStrings.clear();
@@ -266,7 +285,7 @@ bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) {
header->stringCount = pool.size();
header->flags |= android::ResStringPool_header::UTF8_FLAG;
- uint32_t* indices = out->nextBlock<uint32_t>(pool.size());
+ uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr;
uint32_t* styleIndices = nullptr;
if (!pool.mStyles.empty()) {
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 2aa5b65412e6..64772a4ae487 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -158,6 +158,12 @@ public:
StyleRef makeRef(const StyleString& str, const Context& context);
/**
+ * Adds a style from another string pool. Returns a reference to the
+ * style in the string pool.
+ */
+ StyleRef makeRef(const StyleRef& ref);
+
+ /**
* Moves pool into this one without coalescing strings. When this
* function returns, pool will be empty.
*/
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 85d101a76301..9552937d4ad4 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -162,6 +162,16 @@ TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
EXPECT_NE(ref.getIndex(), styleRef.getIndex());
}
+TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
+ StringPool pool;
+ BigBuffer buffer(1024);
+ StringPool::flattenUtf8(&buffer, pool);
+
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ android::ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
+}
+
constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
TEST(StringPoolTest, FlattenUtf8) {
@@ -183,16 +193,10 @@ TEST(StringPoolTest, FlattenUtf8) {
BigBuffer buffer(1024);
StringPool::flattenUtf8(&buffer, pool);
- uint8_t* data = new uint8_t[buffer.size()];
- uint8_t* p = data;
- for (const auto& b : buffer) {
- memcpy(p, b.buffer.get(), b.size);
- p += b.size;
- }
-
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
{
- ResStringPool test;
- ASSERT_TRUE(test.setTo(data, buffer.size()) == NO_ERROR);
+ android::ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
EXPECT_EQ(util::getString(test, 0), u"hello");
EXPECT_EQ(util::getString(test, 1), u"goodbye");
@@ -214,7 +218,6 @@ TEST(StringPoolTest, FlattenUtf8) {
EXPECT_EQ(ResStringPool_span::END, span->name.index);
}
- delete[] data;
}
} // namespace aapt
diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
index b6ca6d5d3b50..dd6f63ad3f65 100644
--- a/tools/aapt2/XmlFlattener.cpp
+++ b/tools/aapt2/XmlFlattener.cpp
@@ -35,6 +35,10 @@
namespace aapt {
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
+constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
+
struct AttributeValueFlattener : ValueVisitor {
struct Args : ValueVisitorArgs {
Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV,
@@ -95,7 +99,9 @@ static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
return a.resourceId < id;
}
-XmlFlattener::XmlFlattener(const std::shared_ptr<Resolver>& resolver) : mResolver(resolver) {
+XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver) :
+ mTable(table), mResolver(resolver) {
}
/**
@@ -190,28 +196,50 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source,
uint32_t nextAttributeId = 0;
const auto endAttrIter = parser->endAttributes();
for (auto attrIter = parser->beginAttributes();
- attrIter != endAttrIter;
- ++attrIter) {
+ attrIter != endAttrIter;
+ ++attrIter) {
uint32_t id;
StringPool::Ref nameRef;
const Attribute* attr = nullptr;
- if (attrIter->namespaceUri.empty()) {
+
+ if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) {
+ size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
+ if (sdkVersion > options.maxSdkAttribute.value()) {
+ // We will silently omit this attribute
+ smallestStrippedAttributeSdk =
+ std::min(smallestStrippedAttributeSdk, sdkVersion);
+ continue;
+ }
+ }
+
+ ResourceNameRef genIdName;
+ bool create = false;
+ bool privateRef = false;
+ if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName,
+ &create, &privateRef) && create) {
+ mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()),
+ util::make_unique<Id>());
+ }
+
+
+ StringPiece16 package;
+ if (util::stringStartsWith<char16_t>(attrIter->namespaceUri, kSchemaPrefix)) {
+ StringPiece16 schemaPrefix = kSchemaPrefix;
+ package = attrIter->namespaceUri;
+ package = package.substr(schemaPrefix.size(),
+ package.size() - schemaPrefix.size());
+ } else if (attrIter->namespaceUri == kSchemaAuto && mResolver) {
+ package = mResolver->getDefaultPackage();
+ }
+
+ if (package.empty() || !mResolver) {
// Attributes that have no resource ID (because they don't belong to a
// package) should appear after those that do have resource IDs. Assign
// them some/ integer value that will appear after.
id = 0x80000000u | nextAttributeId++;
nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
- } else {
- StringPiece16 package;
- if (attrIter->namespaceUri == u"http://schemas.android.com/apk/res-auto") {
- package = mResolver->getDefaultPackage();
- } else {
- // TODO(adamlesinski): Extract package from namespace.
- // The package name appears like so:
- // http://schemas.android.com/apk/res/<package name>
- package = u"android";
- }
+ } else {
// Find the Attribute object via our Resolver.
ResourceName attrName = {
package.toString(), ResourceType::kAttr, attrIter->name };
@@ -236,16 +264,6 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source,
continue;
}
- if (options.maxSdkAttribute && package == u"android") {
- size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
- if (sdkVersion > options.maxSdkAttribute.value()) {
- // We will silently omit this attribute
- smallestStrippedAttributeSdk =
- std::min(smallestStrippedAttributeSdk, sdkVersion);
- continue;
- }
- }
-
id = result.value().id.id;
attr = result.value().attr;
diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h
index abf64ab9d0ee..540a5ef00282 100644
--- a/tools/aapt2/XmlFlattener.h
+++ b/tools/aapt2/XmlFlattener.h
@@ -45,7 +45,8 @@ public:
* Creates a flattener with a Resolver to resolve references
* and attributes.
*/
- XmlFlattener(const std::shared_ptr<Resolver>& resolver);
+ XmlFlattener(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver);
XmlFlattener(const XmlFlattener&) = delete; // Not copyable.
@@ -60,6 +61,7 @@ public:
BigBuffer* outBuffer, Options options);
private:
+ std::shared_ptr<ResourceTable> mTable;
std::shared_ptr<Resolver> mResolver;
};
diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp
index 6e248475749a..a7d7ac6c267a 100644
--- a/tools/aapt2/XmlFlattener_test.cpp
+++ b/tools/aapt2/XmlFlattener_test.cpp
@@ -47,7 +47,7 @@ public:
table->addResource(ResourceName{ {}, ResourceType::kId, u"test" },
ResourceId{ 0x01020000 }, {}, {}, util::make_unique<Id>());
- mFlattener = std::make_shared<XmlFlattener>(
+ mFlattener = std::make_shared<XmlFlattener>(nullptr,
std::make_shared<Resolver>(table, std::make_shared<AssetManager>()));
}
diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp
new file mode 100644
index 000000000000..ad5d84ad9393
--- /dev/null
+++ b/tools/aapt2/ZipEntry.cpp
@@ -0,0 +1,739 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Access to entries in a Zip archive.
+//
+
+#define LOG_TAG "zip"
+
+#include "ZipEntry.h"
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+namespace aapt {
+
+using namespace android;
+
+/*
+ * Initialize a new ZipEntry structure from a FILE* positioned at a
+ * CentralDirectoryEntry.
+ *
+ * On exit, the file pointer will be at the start of the next CDE or
+ * at the EOCD.
+ */
+status_t ZipEntry::initFromCDE(FILE* fp)
+{
+ status_t result;
+ long posn;
+ bool hasDD;
+
+ //ALOGV("initFromCDE ---\n");
+
+ /* read the CDE */
+ result = mCDE.read(fp);
+ if (result != NO_ERROR) {
+ ALOGD("mCDE.read failed\n");
+ return result;
+ }
+
+ //mCDE.dump();
+
+ /* using the info in the CDE, go load up the LFH */
+ posn = ftell(fp);
+ if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
+ ALOGD("local header seek failed (%ld)\n",
+ mCDE.mLocalHeaderRelOffset);
+ return UNKNOWN_ERROR;
+ }
+
+ result = mLFH.read(fp);
+ if (result != NO_ERROR) {
+ ALOGD("mLFH.read failed\n");
+ return result;
+ }
+
+ if (fseek(fp, posn, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ //mLFH.dump();
+
+ /*
+ * We *might* need to read the Data Descriptor at this point and
+ * integrate it into the LFH. If this bit is set, the CRC-32,
+ * compressed size, and uncompressed size will be zero. In practice
+ * these seem to be rare.
+ */
+ hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
+ if (hasDD) {
+ // do something clever
+ //ALOGD("+++ has data descriptor\n");
+ }
+
+ /*
+ * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr"
+ * flag is set, because the LFH is incomplete. (Not a problem, since we
+ * prefer the CDE values.)
+ */
+ if (!hasDD && !compareHeaders()) {
+ ALOGW("warning: header mismatch\n");
+ // keep going?
+ }
+
+ /*
+ * If the mVersionToExtract is greater than 20, we may have an
+ * issue unpacking the record -- could be encrypted, compressed
+ * with something we don't support, or use Zip64 extensions. We
+ * can defer worrying about that to when we're extracting data.
+ */
+
+ return NO_ERROR;
+}
+
+/*
+ * Initialize a new entry. Pass in the file name and an optional comment.
+ *
+ * Initializes the CDE and the LFH.
+ */
+void ZipEntry::initNew(const char* fileName, const char* comment)
+{
+ assert(fileName != NULL && *fileName != '\0'); // name required
+
+ /* most fields are properly initialized by constructor */
+ mCDE.mVersionMadeBy = kDefaultMadeBy;
+ mCDE.mVersionToExtract = kDefaultVersion;
+ mCDE.mCompressionMethod = kCompressStored;
+ mCDE.mFileNameLength = strlen(fileName);
+ if (comment != NULL)
+ mCDE.mFileCommentLength = strlen(comment);
+ mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does
+
+ if (mCDE.mFileNameLength > 0) {
+ mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
+ strcpy((char*) mCDE.mFileName, fileName);
+ }
+ if (mCDE.mFileCommentLength > 0) {
+ /* TODO: stop assuming null-terminated ASCII here? */
+ mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
+ strcpy((char*) mCDE.mFileComment, comment);
+ }
+
+ copyCDEtoLFH();
+}
+
+/*
+ * Initialize a new entry, starting with the ZipEntry from a different
+ * archive.
+ *
+ * Initializes the CDE and the LFH.
+ */
+status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */,
+ const ZipEntry* pEntry)
+{
+ mCDE = pEntry->mCDE;
+ // Check whether we got all the memory needed.
+ if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) ||
+ (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) ||
+ (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) {
+ return NO_MEMORY;
+ }
+
+ /* construct the LFH from the CDE */
+ copyCDEtoLFH();
+
+ /*
+ * The LFH "extra" field is independent of the CDE "extra", so we
+ * handle it here.
+ */
+ assert(mLFH.mExtraField == NULL);
+ mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
+ if (mLFH.mExtraFieldLength > 0) {
+ mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
+ if (mLFH.mExtraField == NULL)
+ return NO_MEMORY;
+ memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
+ mLFH.mExtraFieldLength+1);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Insert pad bytes in the LFH by tweaking the "extra" field. This will
+ * potentially confuse something that put "extra" data in here earlier,
+ * but I can't find an actual problem.
+ */
+status_t ZipEntry::addPadding(int padding)
+{
+ if (padding <= 0)
+ return INVALID_OPERATION;
+
+ //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
+ // padding, mLFH.mExtraFieldLength, mCDE.mFileName);
+
+ if (mLFH.mExtraFieldLength > 0) {
+ /* extend existing field */
+ unsigned char* newExtra;
+
+ newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
+ if (newExtra == NULL)
+ return NO_MEMORY;
+ memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
+ memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
+
+ delete[] mLFH.mExtraField;
+ mLFH.mExtraField = newExtra;
+ mLFH.mExtraFieldLength += padding;
+ } else {
+ /* create new field */
+ mLFH.mExtraField = new unsigned char[padding];
+ memset(mLFH.mExtraField, 0, padding);
+ mLFH.mExtraFieldLength = padding;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Set the fields in the LFH equal to the corresponding fields in the CDE.
+ *
+ * This does not touch the LFH "extra" field.
+ */
+void ZipEntry::copyCDEtoLFH(void)
+{
+ mLFH.mVersionToExtract = mCDE.mVersionToExtract;
+ mLFH.mGPBitFlag = mCDE.mGPBitFlag;
+ mLFH.mCompressionMethod = mCDE.mCompressionMethod;
+ mLFH.mLastModFileTime = mCDE.mLastModFileTime;
+ mLFH.mLastModFileDate = mCDE.mLastModFileDate;
+ mLFH.mCRC32 = mCDE.mCRC32;
+ mLFH.mCompressedSize = mCDE.mCompressedSize;
+ mLFH.mUncompressedSize = mCDE.mUncompressedSize;
+ mLFH.mFileNameLength = mCDE.mFileNameLength;
+ // the "extra field" is independent
+
+ delete[] mLFH.mFileName;
+ if (mLFH.mFileNameLength > 0) {
+ mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
+ strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
+ } else {
+ mLFH.mFileName = NULL;
+ }
+}
+
+/*
+ * Set some information about a file after we add it.
+ */
+void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod)
+{
+ mCDE.mCompressionMethod = compressionMethod;
+ mCDE.mCRC32 = crc32;
+ mCDE.mCompressedSize = compLen;
+ mCDE.mUncompressedSize = uncompLen;
+ mCDE.mCompressionMethod = compressionMethod;
+ if (compressionMethod == kCompressDeflated) {
+ mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used
+ }
+ copyCDEtoLFH();
+}
+
+/*
+ * See if the data in mCDE and mLFH match up. This is mostly useful for
+ * debugging these classes, but it can be used to identify damaged
+ * archives.
+ *
+ * Returns "false" if they differ.
+ */
+bool ZipEntry::compareHeaders(void) const
+{
+ if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
+ ALOGV("cmp: VersionToExtract\n");
+ return false;
+ }
+ if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
+ ALOGV("cmp: GPBitFlag\n");
+ return false;
+ }
+ if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
+ ALOGV("cmp: CompressionMethod\n");
+ return false;
+ }
+ if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
+ ALOGV("cmp: LastModFileTime\n");
+ return false;
+ }
+ if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
+ ALOGV("cmp: LastModFileDate\n");
+ return false;
+ }
+ if (mCDE.mCRC32 != mLFH.mCRC32) {
+ ALOGV("cmp: CRC32\n");
+ return false;
+ }
+ if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
+ ALOGV("cmp: CompressedSize\n");
+ return false;
+ }
+ if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
+ ALOGV("cmp: UncompressedSize\n");
+ return false;
+ }
+ if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
+ ALOGV("cmp: FileNameLength\n");
+ return false;
+ }
+#if 0 // this seems to be used for padding, not real data
+ if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
+ ALOGV("cmp: ExtraFieldLength\n");
+ return false;
+ }
+#endif
+ if (mCDE.mFileName != NULL) {
+ if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
+ ALOGV("cmp: FileName\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/*
+ * Convert the DOS date/time stamp into a UNIX time stamp.
+ */
+time_t ZipEntry::getModWhen(void) const
+{
+ struct tm parts;
+
+ parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
+ parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
+ parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
+ parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
+ parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
+ parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
+ parts.tm_wday = parts.tm_yday = 0;
+ parts.tm_isdst = -1; // DST info "not available"
+
+ return mktime(&parts);
+}
+
+/*
+ * Set the CDE/LFH timestamp from UNIX time.
+ */
+void ZipEntry::setModWhen(time_t when)
+{
+#if !defined(_WIN32)
+ struct tm tmResult;
+#endif
+ time_t even;
+ unsigned short zdate, ztime;
+
+ struct tm* ptm;
+
+ /* round up to an even number of seconds */
+ even = (time_t)(((unsigned long)(when) + 1) & (~1));
+
+ /* expand */
+#if !defined(_WIN32)
+ ptm = localtime_r(&even, &tmResult);
+#else
+ ptm = localtime(&even);
+#endif
+
+ int year;
+ year = ptm->tm_year;
+ if (year < 80)
+ year = 80;
+
+ zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
+ ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
+
+ mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
+ mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::LocalFileHeader
+ * ===========================================================================
+ */
+
+/*
+ * Read a local file header.
+ *
+ * On entry, "fp" points to the signature at the start of the header.
+ * On exit, "fp" points to the start of data.
+ */
+status_t ZipEntry::LocalFileHeader::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kLFHLen];
+
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+
+ if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ ALOGD("whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
+
+ // TODO: validate sizes
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* grab extra field */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a local file header.
+ */
+status_t ZipEntry::LocalFileHeader::write(FILE* fp)
+{
+ unsigned char buf[kLFHLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x0e], mCRC32);
+ ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
+
+ if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Dump the contents of a LocalFileHeader object.
+ */
+void ZipEntry::LocalFileHeader::dump(void) const
+{
+ ALOGD(" LocalFileHeader contents:\n");
+ ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ ALOGD(" filenameLen=%u extraLen=%u\n",
+ mFileNameLength, mExtraFieldLength);
+ if (mFileName != NULL)
+ ALOGD(" filename: '%s'\n", mFileName);
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::CentralDirEntry
+ * ===========================================================================
+ */
+
+/*
+ * Read the central dir entry that appears next in the file.
+ *
+ * On entry, "fp" should be positioned on the signature bytes for the
+ * entry. On exit, "fp" will point at the signature word for the next
+ * entry or for the EOCD.
+ */
+status_t ZipEntry::CentralDirEntry::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kCDELen];
+
+ /* no re-use */
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+ assert(mFileComment == NULL);
+
+ if (fread(buf, 1, kCDELen, fp) != kCDELen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ ALOGD("Whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
+ mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
+ mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
+ mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
+ mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
+ mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
+
+ // TODO: validate sizes and offsets
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* read "extra field" */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+
+ /* grab comment, if any */
+ if (mFileCommentLength != 0) {
+ mFileComment = new unsigned char[mFileCommentLength+1];
+ if (mFileComment == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileComment[mFileCommentLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a central dir entry.
+ */
+status_t ZipEntry::CentralDirEntry::write(FILE* fp)
+{
+ unsigned char buf[kCDELen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
+ ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x10], mCRC32);
+ ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
+ ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
+ ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
+ ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
+ ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
+ ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
+
+ if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write comment */
+ if (mFileCommentLength != 0) {
+ if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of a CentralDirEntry object.
+ */
+void ZipEntry::CentralDirEntry::dump(void) const
+{
+ ALOGD(" CentralDirEntry contents:\n");
+ ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n",
+ mFileNameLength, mExtraFieldLength, mFileCommentLength);
+ ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
+ mDiskNumberStart, mInternalAttrs, mExternalAttrs,
+ mLocalHeaderRelOffset);
+
+ if (mFileName != NULL)
+ ALOGD(" filename: '%s'\n", mFileName);
+ if (mFileComment != NULL)
+ ALOGD(" comment: '%s'\n", mFileComment);
+}
+
+/*
+ * Copy-assignment operator for CentralDirEntry.
+ */
+ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) {
+ if (this == &src) {
+ return *this;
+ }
+
+ // Free up old data.
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+
+ // Copy scalars.
+ mVersionMadeBy = src.mVersionMadeBy;
+ mVersionToExtract = src.mVersionToExtract;
+ mGPBitFlag = src.mGPBitFlag;
+ mCompressionMethod = src.mCompressionMethod;
+ mLastModFileTime = src.mLastModFileTime;
+ mLastModFileDate = src.mLastModFileDate;
+ mCRC32 = src.mCRC32;
+ mCompressedSize = src.mCompressedSize;
+ mUncompressedSize = src.mUncompressedSize;
+ mFileNameLength = src.mFileNameLength;
+ mExtraFieldLength = src.mExtraFieldLength;
+ mFileCommentLength = src.mFileCommentLength;
+ mDiskNumberStart = src.mDiskNumberStart;
+ mInternalAttrs = src.mInternalAttrs;
+ mExternalAttrs = src.mExternalAttrs;
+ mLocalHeaderRelOffset = src.mLocalHeaderRelOffset;
+
+ // Copy strings, if necessary.
+ if (mFileNameLength > 0) {
+ mFileName = new unsigned char[mFileNameLength + 1];
+ if (mFileName != NULL)
+ strcpy((char*)mFileName, (char*)src.mFileName);
+ } else {
+ mFileName = NULL;
+ }
+ if (mFileCommentLength > 0) {
+ mFileComment = new unsigned char[mFileCommentLength + 1];
+ if (mFileComment != NULL)
+ strcpy((char*)mFileComment, (char*)src.mFileComment);
+ } else {
+ mFileComment = NULL;
+ }
+ if (mExtraFieldLength > 0) {
+ /* we null-terminate this, though it may not be a string */
+ mExtraField = new unsigned char[mExtraFieldLength + 1];
+ if (mExtraField != NULL)
+ memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1);
+ } else {
+ mExtraField = NULL;
+ }
+
+ return *this;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h
new file mode 100644
index 000000000000..d048a3ed734e
--- /dev/null
+++ b/tools/aapt2/ZipEntry.h
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Zip archive entries.
+//
+// The ZipEntry class is tightly meshed with the ZipFile class.
+//
+#ifndef __LIBS_ZIPENTRY_H
+#define __LIBS_ZIPENTRY_H
+
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+namespace aapt {
+
+using android::status_t;
+
+class ZipFile;
+
+/*
+ * ZipEntry objects represent a single entry in a Zip archive.
+ *
+ * You can use one of these to get or set information about an entry, but
+ * there are no functions here for accessing the data itself. (We could
+ * tuck a pointer to the ZipFile in here for convenience, but that raises
+ * the likelihood of using ZipEntry objects after discarding the ZipFile.)
+ *
+ * File information is stored in two places: next to the file data (the Local
+ * File Header, and possibly a Data Descriptor), and at the end of the file
+ * (the Central Directory Entry). The two must be kept in sync.
+ */
+class ZipEntry {
+public:
+ friend class ZipFile;
+
+ ZipEntry(void)
+ : mDeleted(false), mMarked(false)
+ {}
+ ~ZipEntry(void) {}
+
+ /*
+ * Returns "true" if the data is compressed.
+ */
+ bool isCompressed(void) const {
+ return mCDE.mCompressionMethod != kCompressStored;
+ }
+ int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
+
+ /*
+ * Return the uncompressed length.
+ */
+ off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
+
+ /*
+ * Return the compressed length. For uncompressed data, this returns
+ * the same thing as getUncompresesdLen().
+ */
+ off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
+
+ /*
+ * Return the offset of the local file header.
+ */
+ off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
+
+ /*
+ * Return the absolute file offset of the start of the compressed or
+ * uncompressed data.
+ */
+ off_t getFileOffset(void) const {
+ return mCDE.mLocalHeaderRelOffset +
+ LocalFileHeader::kLFHLen +
+ mLFH.mFileNameLength +
+ mLFH.mExtraFieldLength;
+ }
+
+ /*
+ * Return the data CRC.
+ */
+ unsigned long getCRC32(void) const { return mCDE.mCRC32; }
+
+ /*
+ * Return file modification time in UNIX seconds-since-epoch.
+ */
+ time_t getModWhen(void) const;
+
+ /*
+ * Return the archived file name.
+ */
+ const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
+
+ /*
+ * Application-defined "mark". Can be useful when synchronizing the
+ * contents of an archive with contents on disk.
+ */
+ bool getMarked(void) const { return mMarked; }
+ void setMarked(bool val) { mMarked = val; }
+
+ /*
+ * Some basic functions for raw data manipulation. "LE" means
+ * Little Endian.
+ */
+ static inline unsigned short getShortLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8);
+ }
+ static inline unsigned long getLongLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+ static inline void putShortLE(unsigned char* buf, short val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ }
+ static inline void putLongLE(unsigned char* buf, long val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ buf[2] = (unsigned char) (val >> 16);
+ buf[3] = (unsigned char) (val >> 24);
+ }
+
+ /* defined for Zip archives */
+ enum {
+ kCompressStored = 0, // no compression
+ // shrunk = 1,
+ // reduced 1 = 2,
+ // reduced 2 = 3,
+ // reduced 3 = 4,
+ // reduced 4 = 5,
+ // imploded = 6,
+ // tokenized = 7,
+ kCompressDeflated = 8, // standard deflate
+ // Deflate64 = 9,
+ // lib imploded = 10,
+ // reserved = 11,
+ // bzip2 = 12,
+ };
+
+ /*
+ * Deletion flag. If set, the entry will be removed on the next
+ * call to "flush".
+ */
+ bool getDeleted(void) const { return mDeleted; }
+
+protected:
+ /*
+ * Initialize the structure from the file, which is pointing at
+ * our Central Directory entry.
+ */
+ status_t initFromCDE(FILE* fp);
+
+ /*
+ * Initialize the structure for a new file. We need the filename
+ * and comment so that we can properly size the LFH area. The
+ * filename is mandatory, the comment is optional.
+ */
+ void initNew(const char* fileName, const char* comment);
+
+ /*
+ * Initialize the structure with the contents of a ZipEntry from
+ * another file.
+ */
+ status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry);
+
+ /*
+ * Add some pad bytes to the LFH. We do this by adding or resizing
+ * the "extra" field.
+ */
+ status_t addPadding(int padding);
+
+ /*
+ * Set information about the data for this entry.
+ */
+ void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod);
+
+ /*
+ * Set the modification date.
+ */
+ void setModWhen(time_t when);
+
+ /*
+ * Set the offset of the local file header, relative to the start of
+ * the current file.
+ */
+ void setLFHOffset(off_t offset) {
+ mCDE.mLocalHeaderRelOffset = (long) offset;
+ }
+
+ /* mark for deletion; used by ZipFile::remove() */
+ void setDeleted(void) { mDeleted = true; }
+
+private:
+ /* these are private and not defined */
+ ZipEntry(const ZipEntry& src);
+ ZipEntry& operator=(const ZipEntry& src);
+
+ /* returns "true" if the CDE and the LFH agree */
+ bool compareHeaders(void) const;
+ void copyCDEtoLFH(void);
+
+ bool mDeleted; // set if entry is pending deletion
+ bool mMarked; // app-defined marker
+
+ /*
+ * Every entry in the Zip archive starts off with one of these.
+ */
+ class LocalFileHeader {
+ public:
+ LocalFileHeader(void) :
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileName(NULL),
+ mExtraField(NULL)
+ {}
+ virtual ~LocalFileHeader(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+
+ enum {
+ kSignature = 0x04034b50,
+ kLFHLen = 30, // LocalFileHdr len, excl. var fields
+ };
+
+ void dump(void) const;
+ };
+
+ /*
+ * Every entry in the Zip archive has one of these in the "central
+ * directory" at the end of the file.
+ */
+ class CentralDirEntry {
+ public:
+ CentralDirEntry(void) :
+ mVersionMadeBy(0),
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileCommentLength(0),
+ mDiskNumberStart(0),
+ mInternalAttrs(0),
+ mExternalAttrs(0),
+ mLocalHeaderRelOffset(0),
+ mFileName(NULL),
+ mExtraField(NULL),
+ mFileComment(NULL)
+ {}
+ virtual ~CentralDirEntry(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ CentralDirEntry& operator=(const CentralDirEntry& src);
+
+ // unsigned long mSignature;
+ unsigned short mVersionMadeBy;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned short mFileCommentLength;
+ unsigned short mDiskNumberStart;
+ unsigned short mInternalAttrs;
+ unsigned long mExternalAttrs;
+ unsigned long mLocalHeaderRelOffset;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+ unsigned char* mFileComment;
+
+ void dump(void) const;
+
+ enum {
+ kSignature = 0x02014b50,
+ kCDELen = 46, // CentralDirEnt len, excl. var fields
+ };
+ };
+
+ enum {
+ //kDataDescriptorSignature = 0x08074b50, // currently unused
+ kDataDescriptorLen = 16, // four 32-bit fields
+
+ kDefaultVersion = 20, // need deflate, nothing much else
+ kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3
+ kUsesDataDescr = 0x0008, // GPBitFlag bit 3
+ };
+
+ LocalFileHeader mLFH;
+ CentralDirEntry mCDE;
+};
+
+}; // namespace aapt
+
+#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp
new file mode 100644
index 000000000000..41e59cf6240e
--- /dev/null
+++ b/tools/aapt2/ZipFile.cpp
@@ -0,0 +1,1305 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Access to Zip archives.
+//
+
+#define LOG_TAG "zip"
+
+#include <androidfw/ZipUtils.h>
+#include <utils/Log.h>
+
+#include "ZipFile.h"
+#include "Util.h"
+
+#include <zlib.h>
+#define DEF_MEM_LEVEL 8 // normally in zutil.h?
+
+#include <memory.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <assert.h>
+
+namespace aapt {
+
+using namespace android;
+
+/*
+ * Some environments require the "b", some choke on it.
+ */
+#define FILE_OPEN_RO "rb"
+#define FILE_OPEN_RW "r+b"
+#define FILE_OPEN_RW_CREATE "w+b"
+
+/* should live somewhere else? */
+static status_t errnoToStatus(int err)
+{
+ if (err == ENOENT)
+ return NAME_NOT_FOUND;
+ else if (err == EACCES)
+ return PERMISSION_DENIED;
+ else
+ return UNKNOWN_ERROR;
+}
+
+/*
+ * Open a file and parse its guts.
+ */
+status_t ZipFile::open(const char* zipFileName, int flags)
+{
+ bool newArchive = false;
+
+ assert(mZipFp == NULL); // no reopen
+
+ if ((flags & kOpenTruncate))
+ flags |= kOpenCreate; // trunc implies create
+
+ if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
+ return INVALID_OPERATION; // not both
+ if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
+ return INVALID_OPERATION; // not neither
+ if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
+ return INVALID_OPERATION; // create requires write
+
+ if (flags & kOpenTruncate) {
+ newArchive = true;
+ } else {
+ newArchive = (access(zipFileName, F_OK) != 0);
+ if (!(flags & kOpenCreate) && newArchive) {
+ /* not creating, must already exist */
+ ALOGD("File %s does not exist", zipFileName);
+ return NAME_NOT_FOUND;
+ }
+ }
+
+ /* open the file */
+ const char* openflags;
+ if (flags & kOpenReadWrite) {
+ if (newArchive)
+ openflags = FILE_OPEN_RW_CREATE;
+ else
+ openflags = FILE_OPEN_RW;
+ } else {
+ openflags = FILE_OPEN_RO;
+ }
+ mZipFp = fopen(zipFileName, openflags);
+ if (mZipFp == NULL) {
+ int err = errno;
+ ALOGD("fopen failed: %d\n", err);
+ return errnoToStatus(err);
+ }
+
+ status_t result;
+ if (!newArchive) {
+ /*
+ * Load the central directory. If that fails, then this probably
+ * isn't a Zip archive.
+ */
+ result = readCentralDir();
+ } else {
+ /*
+ * Newly-created. The EndOfCentralDir constructor actually
+ * sets everything to be the way we want it (all zeroes). We
+ * set mNeedCDRewrite so that we create *something* if the
+ * caller doesn't add any files. (We could also just unlink
+ * the file if it's brand new and nothing was added, but that's
+ * probably doing more than we really should -- the user might
+ * have a need for empty zip files.)
+ */
+ mNeedCDRewrite = true;
+ result = NO_ERROR;
+ }
+
+ if (flags & kOpenReadOnly)
+ mReadOnly = true;
+ else
+ assert(!mReadOnly);
+
+ return result;
+}
+
+/*
+ * Return the Nth entry in the archive.
+ */
+ZipEntry* ZipFile::getEntryByIndex(int idx) const
+{
+ if (idx < 0 || idx >= (int) mEntries.size())
+ return NULL;
+
+ return mEntries[idx];
+}
+
+/*
+ * Find an entry by name.
+ */
+ZipEntry* ZipFile::getEntryByName(const char* fileName) const
+{
+ /*
+ * Do a stupid linear string-compare search.
+ *
+ * There are various ways to speed this up, especially since it's rare
+ * to intermingle changes to the archive with "get by name" calls. We
+ * don't want to sort the mEntries vector itself, however, because
+ * it's used to recreate the Central Directory.
+ *
+ * (Hash table works, parallel list of pointers in sorted order is good.)
+ */
+ int idx;
+
+ for (idx = mEntries.size()-1; idx >= 0; idx--) {
+ ZipEntry* pEntry = mEntries[idx];
+ if (!pEntry->getDeleted() &&
+ strcmp(fileName, pEntry->getFileName()) == 0)
+ {
+ return pEntry;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Empty the mEntries vector.
+ */
+void ZipFile::discardEntries(void)
+{
+ int count = mEntries.size();
+
+ while (--count >= 0)
+ delete mEntries[count];
+
+ mEntries.clear();
+}
+
+
+/*
+ * Find the central directory and read the contents.
+ *
+ * The fun thing about ZIP archives is that they may or may not be
+ * readable from start to end. In some cases, notably for archives
+ * that were written to stdout, the only length information is in the
+ * central directory at the end of the file.
+ *
+ * Of course, the central directory can be followed by a variable-length
+ * comment field, so we have to scan through it backwards. The comment
+ * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
+ * itself, plus apparently sometimes people throw random junk on the end
+ * just for the fun of it.
+ *
+ * This is all a little wobbly. If the wrong value ends up in the EOCD
+ * area, we're hosed. This appears to be the way that everbody handles
+ * it though, so we're in pretty good company if this fails.
+ */
+status_t ZipFile::readCentralDir(void)
+{
+ status_t result = NO_ERROR;
+ unsigned char* buf = NULL;
+ off_t fileLength, seekStart;
+ long readAmount;
+ int i;
+
+ fseek(mZipFp, 0, SEEK_END);
+ fileLength = ftell(mZipFp);
+ rewind(mZipFp);
+
+ /* too small to be a ZIP archive? */
+ if (fileLength < EndOfCentralDir::kEOCDLen) {
+ ALOGD("Length is %ld -- too small\n", (long)fileLength);
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
+ if (buf == NULL) {
+ ALOGD("Failure allocating %d bytes for EOCD search",
+ EndOfCentralDir::kMaxEOCDSearch);
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
+ seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
+ readAmount = EndOfCentralDir::kMaxEOCDSearch;
+ } else {
+ seekStart = 0;
+ readAmount = (long) fileLength;
+ }
+ if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
+ ALOGD("Failure seeking to end of zip at %ld", (long) seekStart);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* read the last part of the file into the buffer */
+ if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
+ ALOGD("short file? wanted %ld\n", readAmount);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* find the end-of-central-dir magic */
+ for (i = readAmount - 4; i >= 0; i--) {
+ if (buf[i] == 0x50 &&
+ ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
+ {
+ ALOGV("+++ Found EOCD at buf+%d\n", i);
+ break;
+ }
+ }
+ if (i < 0) {
+ ALOGD("EOCD not found, not Zip\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /* extract eocd values */
+ result = mEOCD.readBuf(buf + i, readAmount - i);
+ if (result != NO_ERROR) {
+ ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
+ goto bail;
+ }
+ //mEOCD.dump();
+
+ if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
+ mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
+ {
+ ALOGD("Archive spanning not supported\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /*
+ * So far so good. "mCentralDirSize" is the size in bytes of the
+ * central directory, so we can just seek back that far to find it.
+ * We can also seek forward mCentralDirOffset bytes from the
+ * start of the file.
+ *
+ * We're not guaranteed to have the rest of the central dir in the
+ * buffer, nor are we guaranteed that the central dir will have any
+ * sort of convenient size. We need to skip to the start of it and
+ * read the header, then the other goodies.
+ *
+ * The only thing we really need right now is the file comment, which
+ * we're hoping to preserve.
+ */
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ ALOGD("Failure seeking to central dir offset %ld\n",
+ mEOCD.mCentralDirOffset);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Loop through and read the central dir entries.
+ */
+ ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
+ int entry;
+ for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
+ ZipEntry* pEntry = new ZipEntry;
+
+ result = pEntry->initFromCDE(mZipFp);
+ if (result != NO_ERROR) {
+ ALOGD("initFromCDE failed\n");
+ delete pEntry;
+ goto bail;
+ }
+
+ mEntries.push_back(pEntry);
+ }
+
+
+ /*
+ * If all went well, we should now be back at the EOCD.
+ */
+ {
+ unsigned char checkBuf[4];
+ if (fread(checkBuf, 1, 4, mZipFp) != 4) {
+ ALOGD("EOCD check read failed\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+ if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
+ ALOGD("EOCD read check failed\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ ALOGV("+++ EOCD read check passed\n");
+ }
+
+bail:
+ delete[] buf;
+ return result;
+}
+
+status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod,
+ ZipEntry** ppEntry) {
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry);
+}
+
+
+/*
+ * Add a new file to the archive.
+ *
+ * This requires creating and populating a ZipEntry structure, and copying
+ * the data into the file at the appropriate position. The "appropriate
+ * position" is the current location of the central directory, which we
+ * casually overwrite (we can put it back later).
+ *
+ * If we were concerned about safety, we would want to make all changes
+ * in a temp file and then overwrite the original after everything was
+ * safely written. Not really a concern for us.
+ */
+status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result = NO_ERROR;
+ long lfhPosn, startPosn, endPosn, uncompressedLen;
+ FILE* inputFp = NULL;
+ unsigned long crc;
+ time_t modWhen;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ assert(compressionMethod == ZipEntry::kCompressDeflated ||
+ compressionMethod == ZipEntry::kCompressStored);
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ /* make sure it doesn't already exist */
+ if (getEntryByName(storageName) != NULL)
+ return ALREADY_EXISTS;
+
+ if (!data) {
+ inputFp = fopen(fileName, FILE_OPEN_RO);
+ if (inputFp == NULL)
+ return errnoToStatus(errno);
+ }
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ pEntry->initNew(storageName, NULL);
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH, even though it's still mostly blank. We need it
+ * as a place-holder. In theory the LFH isn't necessary, but in
+ * practice some utilities demand it.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+ startPosn = ftell(mZipFp);
+
+ /*
+ * Copy the data in, possibly compressing it as we go.
+ */
+ if (sourceType == ZipEntry::kCompressStored) {
+ if (compressionMethod == ZipEntry::kCompressDeflated) {
+ bool failed = false;
+ result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
+ if (result != NO_ERROR) {
+ ALOGD("compression failed, storing\n");
+ failed = true;
+ } else {
+ /*
+ * Make sure it has compressed "enough". This probably ought
+ * to be set through an API call, but I don't expect our
+ * criteria to change over time.
+ */
+ long src = inputFp ? ftell(inputFp) : size;
+ long dst = ftell(mZipFp) - startPosn;
+ if (dst + (dst / 10) > src) {
+ ALOGD("insufficient compression (src=%ld dst=%ld), storing\n",
+ src, dst);
+ failed = true;
+ }
+ }
+
+ if (failed) {
+ compressionMethod = ZipEntry::kCompressStored;
+ if (inputFp) rewind(inputFp);
+ fseek(mZipFp, startPosn, SEEK_SET);
+ /* fall through to kCompressStored case */
+ }
+ }
+ /* handle "no compression" request, or failed compression from above */
+ if (compressionMethod == ZipEntry::kCompressStored) {
+ if (inputFp) {
+ result = copyFpToFp(mZipFp, inputFp, &crc);
+ } else {
+ result = copyDataToFp(mZipFp, data, size, &crc);
+ }
+ if (result != NO_ERROR) {
+ // don't need to truncate; happens in CDE rewrite
+ ALOGD("failed copying data in\n");
+ goto bail;
+ }
+ }
+
+ // currently seeked to end of file
+ uncompressedLen = inputFp ? ftell(inputFp) : size;
+ } else if (sourceType == ZipEntry::kCompressDeflated) {
+ /* we should support uncompressed-from-compressed, but it's not
+ * important right now */
+ assert(compressionMethod == ZipEntry::kCompressDeflated);
+
+ bool scanResult;
+ int method;
+ long compressedLen;
+
+ scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
+ &compressedLen, &crc);
+ if (!scanResult || method != ZipEntry::kCompressDeflated) {
+ ALOGD("this isn't a deflated gzip file?");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
+ if (result != NO_ERROR) {
+ ALOGD("failed copying gzip data in\n");
+ goto bail;
+ }
+ } else {
+ assert(false);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * We could write the "Data Descriptor", but there doesn't seem to
+ * be any point since we're going to go back and write the LFH.
+ *
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp); // seeked to end of compressed data
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
+ compressionMethod);
+ modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
+ pEntry->setModWhen(modWhen);
+ pEntry->setLFHOffset(lfhPosn);
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Go back and write the LFH.
+ */
+ if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.push_back(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+bail:
+ if (inputFp != NULL)
+ fclose(inputFp);
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result;
+ long lfhPosn, endPosn;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ if (pEntry == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ result = pEntry->initFromExternal(pSourceZip, pSourceEntry);
+ if (result != NO_ERROR)
+ goto bail;
+ if (padding != 0) {
+ result = pEntry->addPadding(padding);
+ if (result != NO_ERROR)
+ goto bail;
+ }
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH. Since we're not recompressing the data, we already
+ * have all of the fields filled out.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Copy the data over.
+ *
+ * If the "has data descriptor" flag is set, we want to copy the DD
+ * fields as well. This is a fixed-size area immediately following
+ * the data.
+ */
+ if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ off_t copyLen;
+ copyLen = pSourceEntry->getCompressedLen();
+ if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
+ copyLen += ZipEntry::kDataDescriptorLen;
+
+ if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
+ != NO_ERROR)
+ {
+ ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp);
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.push_back(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+ result = NO_ERROR;
+
+bail:
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data.
+ */
+status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (1) {
+ count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
+ if (ferror(srcFp) || ferror(dstFp))
+ return errnoToStatus(errno);
+ if (count == 0)
+ break;
+
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ ALOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "dstFp" will be seeked immediately past the data.
+ */
+status_t ZipFile::copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+ if (size > 0) {
+ *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
+ if (fwrite(data, 1, size, dstFp) != size) {
+ ALOGD("fwrite %d bytes failed\n", (int) size);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy some of the bytes in "src" to "dst".
+ *
+ * If "pCRC32" is NULL, the CRC will not be computed.
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data just written.
+ */
+status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (length) {
+ long readSize;
+
+ readSize = sizeof(tmpBuf);
+ if (readSize > length)
+ readSize = length;
+
+ count = fread(tmpBuf, 1, readSize, srcFp);
+ if ((long) count != readSize) { // error or unexpected EOF
+ ALOGD("fread %d bytes failed\n", (int) readSize);
+ return UNKNOWN_ERROR;
+ }
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ ALOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+
+ length -= readSize;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Compress all of the data in "srcFp" and write it to "dstFp".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the compressed data.
+ */
+status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ status_t result = NO_ERROR;
+ const size_t kBufSize = 32768;
+ unsigned char* inBuf = NULL;
+ unsigned char* outBuf = NULL;
+ z_stream zstream;
+ bool atEof = false; // no feof() aviailable yet
+ unsigned long crc;
+ int zerr;
+
+ /*
+ * Create an input buffer and an output buffer.
+ */
+ inBuf = new unsigned char[kBufSize];
+ outBuf = new unsigned char[kBufSize];
+ if (inBuf == NULL || outBuf == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ /*
+ * Initialize the zlib stream.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = NULL;
+ zstream.avail_in = 0;
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ zstream.data_type = Z_UNKNOWN;
+
+ zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
+ Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+ if (zerr != Z_OK) {
+ result = UNKNOWN_ERROR;
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ crc = crc32(0L, Z_NULL, 0);
+
+ /*
+ * Loop while we have data.
+ */
+ do {
+ size_t getSize;
+ int flush;
+
+ /* only read if the input buffer is empty */
+ if (zstream.avail_in == 0 && !atEof) {
+ ALOGV("+++ reading %d bytes\n", (int)kBufSize);
+ if (data) {
+ getSize = size > kBufSize ? kBufSize : size;
+ memcpy(inBuf, data, getSize);
+ data = ((const char*)data) + getSize;
+ size -= getSize;
+ } else {
+ getSize = fread(inBuf, 1, kBufSize, srcFp);
+ if (ferror(srcFp)) {
+ ALOGD("deflate read failed (errno=%d)\n", errno);
+ goto z_bail;
+ }
+ }
+ if (getSize < kBufSize) {
+ ALOGV("+++ got %d bytes, EOF reached\n",
+ (int)getSize);
+ atEof = true;
+ }
+
+ crc = crc32(crc, inBuf, getSize);
+
+ zstream.next_in = inBuf;
+ zstream.avail_in = getSize;
+ }
+
+ if (atEof)
+ flush = Z_FINISH; /* tell zlib that we're done */
+ else
+ flush = Z_NO_FLUSH; /* more to come! */
+
+ zerr = deflate(&zstream, flush);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ ALOGD("zlib deflate call failed (zerr=%d)\n", zerr);
+ result = UNKNOWN_ERROR;
+ goto z_bail;
+ }
+
+ /* write when we're full or when we're done */
+ if (zstream.avail_out == 0 ||
+ (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize))
+ {
+ ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf));
+ if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) !=
+ (size_t)(zstream.next_out - outBuf))
+ {
+ ALOGD("write %d failed in deflate\n",
+ (int) (zstream.next_out - outBuf));
+ goto z_bail;
+ }
+
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ }
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ *pCRC32 = crc;
+
+z_bail:
+ deflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ delete[] inBuf;
+ delete[] outBuf;
+
+ return result;
+}
+
+/*
+ * Mark an entry as deleted.
+ *
+ * We will eventually need to crunch the file down, but if several files
+ * are being removed (perhaps as part of an "update" process) we can make
+ * things considerably faster by deferring the removal to "flush" time.
+ */
+status_t ZipFile::remove(ZipEntry* pEntry)
+{
+ /*
+ * Should verify that pEntry is actually part of this archive, and
+ * not some stray ZipEntry from a different file.
+ */
+
+ /* mark entry as deleted, and mark archive as dirty */
+ pEntry->setDeleted();
+ mNeedCDRewrite = true;
+ return NO_ERROR;
+}
+
+/*
+ * Flush any pending writes.
+ *
+ * In particular, this will crunch out deleted entries, and write the
+ * Central Directory and EOCD if we have stomped on them.
+ */
+status_t ZipFile::flush(void)
+{
+ status_t result = NO_ERROR;
+ long eocdPosn;
+ int i, count;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+ if (!mNeedCDRewrite)
+ return NO_ERROR;
+
+ assert(mZipFp != NULL);
+
+ result = crunchArchive();
+ if (result != NO_ERROR)
+ return result;
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ count = mEntries.size();
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ pEntry->mCDE.write(mZipFp);
+ }
+
+ eocdPosn = ftell(mZipFp);
+ mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
+
+ mEOCD.write(mZipFp);
+
+ /*
+ * If we had some stuff bloat up during compression and get replaced
+ * with plain files, or if we deleted some entries, there's a lot
+ * of wasted space at the end of the file. Remove it now.
+ */
+ if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
+ ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
+ // not fatal
+ }
+
+ /* should we clear the "newly added" flag in all entries now? */
+
+ mNeedCDRewrite = false;
+ return NO_ERROR;
+}
+
+/*
+ * Crunch deleted files out of an archive by shifting the later files down.
+ *
+ * Because we're not using a temp file, we do the operation inside the
+ * current file.
+ */
+status_t ZipFile::crunchArchive(void)
+{
+ status_t result = NO_ERROR;
+ int i, count;
+ long delCount, adjust;
+
+#if 0
+ printf("CONTENTS:\n");
+ for (i = 0; i < (int) mEntries.size(); i++) {
+ printf(" %d: lfhOff=%ld del=%d\n",
+ i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
+ }
+ printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset);
+#endif
+
+ /*
+ * Roll through the set of files, shifting them as appropriate. We
+ * could probably get a slight performance improvement by sliding
+ * multiple files down at once (because we could use larger reads
+ * when operating on batches of small files), but it's not that useful.
+ */
+ count = mEntries.size();
+ delCount = adjust = 0;
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ long span;
+
+ if (pEntry->getLFHOffset() != 0) {
+ long nextOffset;
+
+ /* Get the length of this entry by finding the offset
+ * of the next entry. Directory entries don't have
+ * file offsets, so we need to find the next non-directory
+ * entry.
+ */
+ nextOffset = 0;
+ for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
+ nextOffset = mEntries[ii]->getLFHOffset();
+ if (nextOffset == 0)
+ nextOffset = mEOCD.mCentralDirOffset;
+ span = nextOffset - pEntry->getLFHOffset();
+
+ assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
+ } else {
+ /* This is a directory entry. It doesn't have
+ * any actual file contents, so there's no need to
+ * move anything.
+ */
+ span = 0;
+ }
+
+ //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
+ // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
+
+ if (pEntry->getDeleted()) {
+ adjust += span;
+ delCount++;
+
+ delete pEntry;
+ mEntries.erase(mEntries.begin() + i);
+
+ /* adjust loop control */
+ count--;
+ i--;
+ } else if (span != 0 && adjust > 0) {
+ /* shuffle this entry back */
+ //printf("+++ Shuffling '%s' back %ld\n",
+ // pEntry->getFileName(), adjust);
+ result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
+ pEntry->getLFHOffset(), span);
+ if (result != NO_ERROR) {
+ /* this is why you use a temp file */
+ ALOGE("error during crunch - archive is toast\n");
+ return result;
+ }
+
+ pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
+ }
+ }
+
+ /*
+ * Fix EOCD info. We have to wait until the end to do some of this
+ * because we use mCentralDirOffset to determine "span" for the
+ * last entry.
+ */
+ mEOCD.mCentralDirOffset -= adjust;
+ mEOCD.mNumEntries -= delCount;
+ mEOCD.mTotalNumEntries -= delCount;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+
+ assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
+ assert(mEOCD.mNumEntries == count);
+
+ return result;
+}
+
+/*
+ * Works like memmove(), but on pieces of a file.
+ */
+status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
+{
+ if (dst == src || n <= 0)
+ return NO_ERROR;
+
+ unsigned char readBuf[32768];
+
+ if (dst < src) {
+ /* shift stuff toward start of file; must read from start */
+ while (n != 0) {
+ size_t getSize = sizeof(readBuf);
+ if (getSize > n)
+ getSize = n;
+
+ if (fseek(fp, (long) src, SEEK_SET) != 0) {
+ ALOGD("filemove src seek %ld failed\n", (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fread(readBuf, 1, getSize, fp) != getSize) {
+ ALOGD("filemove read %ld off=%ld failed\n",
+ (long) getSize, (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fseek(fp, (long) dst, SEEK_SET) != 0) {
+ ALOGD("filemove dst seek %ld failed\n", (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fwrite(readBuf, 1, getSize, fp) != getSize) {
+ ALOGD("filemove write %ld off=%ld failed\n",
+ (long) getSize, (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ src += getSize;
+ dst += getSize;
+ n -= getSize;
+ }
+ } else {
+ /* shift stuff toward end of file; must read from end */
+ assert(false); // write this someday, maybe
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Get the modification time from a file descriptor.
+ */
+time_t ZipFile::getModTime(int fd)
+{
+ struct stat sb;
+
+ if (fstat(fd, &sb) < 0) {
+ ALOGD("HEY: fstat on fd %d failed\n", fd);
+ return (time_t) -1;
+ }
+
+ return sb.st_mtime;
+}
+
+
+#if 0 /* this is a bad idea */
+/*
+ * Get a copy of the Zip file descriptor.
+ *
+ * We don't allow this if the file was opened read-write because we tend
+ * to leave the file contents in an uncertain state between calls to
+ * flush(). The duplicated file descriptor should only be valid for reads.
+ */
+int ZipFile::getZipFd(void) const
+{
+ if (!mReadOnly)
+ return INVALID_OPERATION;
+ assert(mZipFp != NULL);
+
+ int fd;
+ fd = dup(fileno(mZipFp));
+ if (fd < 0) {
+ ALOGD("didn't work, errno=%d\n", errno);
+ }
+
+ return fd;
+}
+#endif
+
+
+#if 0
+/*
+ * Expand data.
+ */
+bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
+{
+ return false;
+}
+#endif
+
+// free the memory when you're done
+void* ZipFile::uncompress(const ZipEntry* entry)
+{
+ size_t unlen = entry->getUncompressedLen();
+ size_t clen = entry->getCompressedLen();
+
+ void* buf = malloc(unlen);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ fseek(mZipFp, 0, SEEK_SET);
+
+ off_t offset = entry->getFileOffset();
+ if (fseek(mZipFp, offset, SEEK_SET) != 0) {
+ goto bail;
+ }
+
+ switch (entry->getCompressionMethod())
+ {
+ case ZipEntry::kCompressStored: {
+ ssize_t amt = fread(buf, 1, unlen, mZipFp);
+ if (amt != (ssize_t)unlen) {
+ goto bail;
+ }
+#if 0
+ printf("data...\n");
+ const unsigned char* p = (unsigned char*)buf;
+ const unsigned char* end = p+unlen;
+ for (int i=0; i<32 && p < end; i++) {
+ printf("0x%08x ", (int)(offset+(i*0x10)));
+ for (int j=0; j<0x10 && p < end; j++) {
+ printf(" %02x", *p);
+ p++;
+ }
+ printf("\n");
+ }
+#endif
+
+ }
+ break;
+ case ZipEntry::kCompressDeflated: {
+ if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
+ goto bail;
+ }
+ }
+ break;
+ default:
+ goto bail;
+ }
+ return buf;
+
+bail:
+ free(buf);
+ return NULL;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipFile::EndOfCentralDir
+ * ===========================================================================
+ */
+
+/*
+ * Read the end-of-central-dir fields.
+ *
+ * "buf" should be positioned at the EOCD signature, and should contain
+ * the entire EOCD area including the comment.
+ */
+status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
+{
+ /* don't allow re-use */
+ assert(mComment == NULL);
+
+ if (len < kEOCDLen) {
+ /* looks like ZIP file got truncated */
+ ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
+ kEOCDLen, len);
+ return INVALID_OPERATION;
+ }
+
+ /* this should probably be an assert() */
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
+ return UNKNOWN_ERROR;
+
+ mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
+ mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
+ mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
+ mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
+ mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
+ mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
+ mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
+
+ // TODO: validate mCentralDirOffset
+
+ if (mCommentLen > 0) {
+ if (kEOCDLen + mCommentLen > len) {
+ ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
+ kEOCDLen, mCommentLen, len);
+ return UNKNOWN_ERROR;
+ }
+ mComment = new unsigned char[mCommentLen];
+ memcpy(mComment, buf + kEOCDLen, mCommentLen);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Write an end-of-central-directory section.
+ */
+status_t ZipFile::EndOfCentralDir::write(FILE* fp)
+{
+ unsigned char buf[kEOCDLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
+ ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
+ ZipEntry::putShortLE(&buf[0x08], mNumEntries);
+ ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
+ ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
+ ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
+ ZipEntry::putShortLE(&buf[0x14], mCommentLen);
+
+ if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
+ return UNKNOWN_ERROR;
+ if (mCommentLen > 0) {
+ assert(mComment != NULL);
+ if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of an EndOfCentralDir object.
+ */
+void ZipFile::EndOfCentralDir::dump(void) const
+{
+ ALOGD(" EndOfCentralDir contents:\n");
+ ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
+ mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
+ ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n",
+ mCentralDirSize, mCentralDirOffset, mCommentLen);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h
new file mode 100644
index 000000000000..9cbd1fa9adba
--- /dev/null
+++ b/tools/aapt2/ZipFile.h
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// General-purpose Zip archive access. This class allows both reading and
+// writing to Zip archives, including deletion of existing entries.
+//
+#ifndef __LIBS_ZIPFILE_H
+#define __LIBS_ZIPFILE_H
+
+#include "BigBuffer.h"
+#include "ZipEntry.h"
+
+#include <stdio.h>
+#include <utils/Errors.h>
+#include <vector>
+
+namespace aapt {
+
+using android::status_t;
+
+/*
+ * Manipulate a Zip archive.
+ *
+ * Some changes will not be visible in the until until "flush" is called.
+ *
+ * The correct way to update a file archive is to make all changes to a
+ * copy of the archive in a temporary file, and then unlink/rename over
+ * the original after everything completes. Because we're only interested
+ * in using this for packaging, we don't worry about such things. Crashing
+ * after making changes and before flush() completes could leave us with
+ * an unusable Zip archive.
+ */
+class ZipFile {
+public:
+ ZipFile(void)
+ : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
+ {}
+ ~ZipFile(void) {
+ if (!mReadOnly)
+ flush();
+ if (mZipFp != NULL)
+ fclose(mZipFp);
+ discardEntries();
+ }
+
+ /*
+ * Open a new or existing archive.
+ */
+ enum {
+ kOpenReadOnly = 0x01,
+ kOpenReadWrite = 0x02,
+ kOpenCreate = 0x04, // create if it doesn't exist
+ kOpenTruncate = 0x08, // if it exists, empty it
+ };
+ status_t open(const char* zipFileName, int flags);
+
+ /*
+ * Add a file to the end of the archive. Specify whether you want the
+ * library to try to store it compressed.
+ *
+ * If "storageName" is specified, the archive will use that instead
+ * of "fileName".
+ *
+ * If there is already an entry with the same name, the call fails.
+ * Existing entries with the same name must be removed first.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const char* fileName, int compressionMethod,
+ ZipEntry** ppEntry)
+ {
+ return add(fileName, fileName, compressionMethod, ppEntry);
+ }
+ status_t add(const char* fileName, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add a file that is already compressed with gzip.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t addGzip(const char* fileName, const char* storageName,
+ ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressDeflated,
+ ZipEntry::kCompressDeflated, ppEntry);
+ }
+
+ /*
+ * Add a file from an in-memory data buffer.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const void* data, size_t size, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(NULL, data, size, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ status_t add(const BigBuffer& data, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry);
+
+ /*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry);
+
+ /*
+ * Mark an entry as having been removed. It is not actually deleted
+ * from the archive or our internal data structures until flush() is
+ * called.
+ */
+ status_t remove(ZipEntry* pEntry);
+
+ /*
+ * Flush changes. If mNeedCDRewrite is set, this writes the central dir.
+ */
+ status_t flush(void);
+
+ /*
+ * Expand the data into the buffer provided. The buffer must hold
+ * at least <uncompressed len> bytes. Variation expands directly
+ * to a file.
+ *
+ * Returns "false" if an error was encountered in the compressed data.
+ */
+ //bool uncompress(const ZipEntry* pEntry, void* buf) const;
+ //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
+ void* uncompress(const ZipEntry* pEntry);
+
+ /*
+ * Get an entry, by name. Returns NULL if not found.
+ *
+ * Does not return entries pending deletion.
+ */
+ ZipEntry* getEntryByName(const char* fileName) const;
+
+ /*
+ * Get the Nth entry in the archive.
+ *
+ * This will return an entry that is pending deletion.
+ */
+ int getNumEntries(void) const { return mEntries.size(); }
+ ZipEntry* getEntryByIndex(int idx) const;
+
+private:
+ /* these are private and not defined */
+ ZipFile(const ZipFile& src);
+ ZipFile& operator=(const ZipFile& src);
+
+ class EndOfCentralDir {
+ public:
+ EndOfCentralDir(void) :
+ mDiskNumber(0),
+ mDiskWithCentralDir(0),
+ mNumEntries(0),
+ mTotalNumEntries(0),
+ mCentralDirSize(0),
+ mCentralDirOffset(0),
+ mCommentLen(0),
+ mComment(NULL)
+ {}
+ virtual ~EndOfCentralDir(void) {
+ delete[] mComment;
+ }
+
+ status_t readBuf(const unsigned char* buf, int len);
+ status_t write(FILE* fp);
+
+ //unsigned long mSignature;
+ unsigned short mDiskNumber;
+ unsigned short mDiskWithCentralDir;
+ unsigned short mNumEntries;
+ unsigned short mTotalNumEntries;
+ unsigned long mCentralDirSize;
+ unsigned long mCentralDirOffset; // offset from first disk
+ unsigned short mCommentLen;
+ unsigned char* mComment;
+
+ enum {
+ kSignature = 0x06054b50,
+ kEOCDLen = 22, // EndOfCentralDir len, excl. comment
+
+ kMaxCommentLen = 65535, // longest possible in ushort
+ kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
+
+ };
+
+ void dump(void) const;
+ };
+
+
+ /* read all entries in the central dir */
+ status_t readCentralDir(void);
+
+ /* crunch deleted entries out */
+ status_t crunchArchive(void);
+
+ /* clean up mEntries */
+ void discardEntries(void);
+
+ /* common handler for all "add" functions */
+ status_t addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry);
+
+ /* copy all of "srcFp" into "dstFp" */
+ status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
+ /* copy all of "data" into "dstFp" */
+ status_t copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+ /* copy some of "srcFp" into "dstFp" */
+ status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32);
+ /* like memmove(), but on parts of a single file */
+ status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
+ /* compress all of "srcFp" into "dstFp", using Deflate */
+ status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+
+ /* get modification date from a file descriptor */
+ time_t getModTime(int fd);
+
+ /*
+ * We use stdio FILE*, which gives us buffering but makes dealing
+ * with files >2GB awkward. Until we support Zip64, we're fine.
+ */
+ FILE* mZipFp; // Zip file pointer
+
+ /* one of these per file */
+ EndOfCentralDir mEOCD;
+
+ /* did we open this read-only? */
+ bool mReadOnly;
+
+ /* set this when we trash the central dir */
+ bool mNeedCDRewrite;
+
+ /*
+ * One ZipEntry per entry in the zip file. I'm using pointers instead
+ * of objects because it's easier than making operator= work for the
+ * classes and sub-classes.
+ */
+ std::vector<ZipEntry*> mEntries;
+};
+
+}; // namespace aapt
+
+#endif // __LIBS_ZIPFILE_H
diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile
index f296dc1f1b2a..5a2a1d167f1e 100644
--- a/tools/aapt2/data/Makefile
+++ b/tools/aapt2/data/Makefile
@@ -14,6 +14,7 @@ FRAMEWORK := ../../../../../out/target/common/obj/APPS/framework-res_intermediat
LOCAL_PACKAGE := com.android.app
LOCAL_RESOURCE_DIR := res
+LOCAL_LIBS := lib/out/package.apk
LOCAL_OUT := out
LOCAL_GEN := out/gen
@@ -21,13 +22,12 @@ LOCAL_GEN := out/gen
# AAPT2 custom rules.
##
-PRIVATE_ARSC := $(LOCAL_OUT)/resources.arsc
PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
# Eg: framework.apk, etc.
-PRIVATE_LIBS := $(FRAMEWORK)
-$(info PRIVATE_LIBS = $(PRIVATE_LIBS))
+PRIVATE_INCLUDES := $(FRAMEWORK)
+$(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES))
# Eg: gen/com/android/app/R.java
PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
@@ -42,56 +42,32 @@ PRIVATE_RESOURCE_TYPES := \
$(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
-# Eg: drawable, drawable-xhdpi, layout
-PRIVATE_NON_VALUE_RESOURCE_TYPES := $(filter-out values%,$(PRIVATE_RESOURCE_TYPES))
-$(info PRIVATE_NON_VALUE_RESOURCE_TYPES = $(PRIVATE_NON_VALUE_RESOURCE_TYPES))
-
-# Eg: out/values-v4.table, out/drawable-xhdpi.table
-PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.table,$(PRIVATE_RESOURCE_TYPES))
+# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
+PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
-# Eg: out/res/layout/main.xml, out/res/drawable/icon.png
-PRIVATE_INTERMEDIATE_FILES := $(patsubst $(LOCAL_RESOURCE_DIR)/%,$(LOCAL_OUT)/res/%,$(filter-out $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES)))
-$(info PRIVATE_INTERMEDIATE_FILES = $(PRIVATE_INTERMEDIATE_FILES))
-
# Generates rules for collect phase.
# $1: Resource type (values-v4)
-# returns: out/values-v4.table: res/values-v4/styles.xml res/values-v4/colors.xml
+# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
define make-collect-rule
-$(LOCAL_OUT)/$1.table: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
- $(AAPT) collect --package $(LOCAL_PACKAGE) -o $$@ $$^
+$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
+ $(AAPT) compile --package $(LOCAL_PACKAGE) --binding $(LOCAL_GEN) -o $$@ $$^
endef
-# Collect: out/values-v4.table <- res/values-v4/styles.xml res/values-v4/colors.xml
+# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
-# Link: out/resources.arsc <- out/values-v4.table out/drawable-v4.table
-$(PRIVATE_ARSC): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS)
- $(AAPT) link --package $(LOCAL_PACKAGE) $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES)
-
-# Compile Manifest: out/AndroidManifest.xml <- AndroidManifest.xml out/resources.arsc
-$(LOCAL_OUT)/AndroidManifest.xml: AndroidManifest.xml $(PRIVATE_ARSC) $(PRIVATE_LIBS)
- $(AAPT) manifest -I $(PRIVATE_ARSC) $(addprefix -I ,$(PRIVATE_LIBS)) -o $(LOCAL_OUT) AndroidManifest.xml
-
-# Generates rules for compile phase.
-# $1: resource file (res/drawable/icon.png)
-# returns: out/res/drawable/icon.png: res/drawable/icon.png out/resources.arsc
-define make-compile-rule
-$1: $(patsubst $(LOCAL_OUT)/res/%,$(LOCAL_RESOURCE_DIR)/%,$1) $(PRIVATE_ARSC) $(PRIVATE_LIBS)
- $(AAPT) compile --package $(LOCAL_PACKAGE) -I $(PRIVATE_ARSC) $(addprefix -I ,$(PRIVATE_LIBS)) -o $(LOCAL_OUT) $$<
-endef
-
-# Compile: out/res/drawable-xhdpi/icon.png <- res/drawable-xhdpi/icon.png
-$(foreach f,$(PRIVATE_INTERMEDIATE_FILES),$(eval $(call make-compile-rule,$f)))
+# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
+$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml
+ $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS)
# R.java: gen/com/android/app/R.java <- out/resources.arsc
# No action since R.java is generated when out/resources.arsc is.
-$(PRIVATE_R_JAVA): $(PRIVATE_ARSC)
+$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
-$(PRIVATE_APK_ALIGNED): $(PRIVATE_ARSC) $(PRIVATE_INTERMEDIATE_FILES) $(LOCAL_OUT)/AndroidManifest.xml
- cd $(LOCAL_OUT); $(ZIP) $(patsubst $(LOCAL_OUT)/%,%,$(PRIVATE_APK_UNALIGNED)) $(patsubst $(LOCAL_OUT)/%,%,$^)
- $(ZIPALIGN) $(PRIVATE_APK_UNALIGNED) $@
+$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
+ $(ZIPALIGN) $< $@
# Create the out directory if needed.
dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
@@ -100,7 +76,7 @@ dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
java: $(PRIVATE_R_JAVA)
.PHONY: assemble
-assemble: $(LOCAL_OUT)/package.apk
+assemble: $(PRIVATE_APK_ALIGNED)
.PHONY: all
all: assemble java
diff --git a/tools/aapt2/data/lib/AndroidManifest.xml b/tools/aapt2/data/lib/AndroidManifest.xml
new file mode 100644
index 000000000000..c1612e5549fd
--- /dev/null
+++ b/tools/aapt2/data/lib/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.appcompat"/>
diff --git a/tools/aapt2/data/lib/Makefile b/tools/aapt2/data/lib/Makefile
new file mode 100644
index 000000000000..2897ff135b30
--- /dev/null
+++ b/tools/aapt2/data/lib/Makefile
@@ -0,0 +1,81 @@
+##
+# Environment dependent variables
+##
+
+AAPT := aapt2
+ZIPALIGN := zipalign 4
+FRAMEWORK := ../../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk
+
+##
+# Project depenedent variables
+##
+
+LOCAL_PACKAGE := android.appcompat
+LOCAL_RESOURCE_DIR := res
+LOCAL_OUT := out
+LOCAL_GEN := out/gen
+
+##
+# AAPT2 custom rules.
+##
+
+PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
+PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
+
+# Eg: framework.apk, etc.
+PRIVATE_LIBS := $(FRAMEWORK)
+$(info PRIVATE_LIBS = $(PRIVATE_LIBS))
+
+# Eg: gen/com/android/app/R.java
+PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
+$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA))
+
+# Eg: res/drawable/icon.png, res/values/styles.xml
+PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f)
+$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES))
+
+# Eg: drawable, values, layouts
+PRIVATE_RESOURCE_TYPES := \
+ $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
+$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
+
+# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
+PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
+$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
+
+# Generates rules for collect phase.
+# $1: Resource type (values-v4)
+# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
+define make-collect-rule
+$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
+ $(AAPT) compile --package $(LOCAL_PACKAGE) -o $$@ $$^
+endef
+
+# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
+$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
+
+# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
+$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) AndroidManifest.xml
+ $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES)
+
+# R.java: gen/com/android/app/R.java <- out/resources.arsc
+# No action since R.java is generated when out/resources.arsc is.
+$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
+
+# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
+$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
+ $(ZIPALIGN) $< $@
+
+# Create the out directory if needed.
+dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
+
+.PHONY: java
+java: $(PRIVATE_R_JAVA)
+
+.PHONY: assemble
+assemble: $(PRIVATE_APK_ALIGNED)
+
+.PHONY: all
+all: assemble java
+
+.DEFAULT_GOAL := all
diff --git a/tools/aapt2/data/lib/res/values/styles.xml b/tools/aapt2/data/lib/res/values/styles.xml
new file mode 100644
index 000000000000..adb5c4fcd004
--- /dev/null
+++ b/tools/aapt2/data/lib/res/values/styles.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="Platform.AppCompat" parent="@android:style/Theme">
+ <item name="android:windowNoTitle">true</item>
+ </style>
+</resources>
diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml
index 71ce388fdbbf..c5dd27650af7 100644
--- a/tools/aapt2/data/res/values/styles.xml
+++ b/tools/aapt2/data/res/values/styles.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <style name="App" parent="android:Theme.Material">
+ <style name="App" parent="android.appcompat:Platform.AppCompat">
<item name="android:background">@color/primary</item>
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorPrimaryDark">@color/primary_dark</item>
diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot
index a92405d9146d..4741952d2332 100644
--- a/tools/aapt2/process.dot
+++ b/tools/aapt2/process.dot
@@ -19,6 +19,13 @@ digraph aapt {
res_layout_fr_main_xml [label="res/layout-fr/main.xml"];
res_values_fr_strings_xml [label="res/values-fr/strings.xml"];
+ lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green];
+ lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green];
+ lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green];
+ lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green];
+ lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green];
+ out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"];
+
out_package -> package_default;
out_fr_package -> package_fr;
@@ -26,6 +33,7 @@ digraph aapt {
package_default -> out_table_aligned;
package_default -> out_res_layout_main_xml;
package_default -> out_res_layout_v21_main_xml [color=red];
+ package_default -> out_res_layout_lib_main_xml;
package_fr [shape=box,label="Assemble",color=blue];
package_fr -> out_table_fr_aligned;
@@ -44,6 +52,7 @@ digraph aapt {
link_tables [shape=box,label="Link",color=blue];
link_tables -> out_values_table;
link_tables -> out_layout_table;
+ link_tables -> lib_apk_resources_arsc;
out_values_table -> compile_values;
@@ -61,10 +70,11 @@ digraph aapt {
link_fr_tables [shape=box,label="Link",color=blue];
link_fr_tables -> out_values_fr_table;
link_fr_tables -> out_layout_fr_table;
+ link_fr_tables -> lib_apk_resources_arsc;
out_values_fr_table -> compile_values_fr;
- compile_values_fr [shape=box,label="Compile",color=blue];
+ compile_values_fr [shape=box,label="Collect",color=blue];
compile_values_fr -> res_values_fr_strings_xml;
out_layout_fr_table -> collect_xml_fr;
@@ -89,4 +99,10 @@ digraph aapt {
compile_res_layout_fr_main_xml -> res_layout_fr_main_xml;
compile_res_layout_fr_main_xml -> out_table_fr_aligned;
+
+ out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml;
+
+ compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue];
+ compile_res_layout_lib_main_xml -> out_table_aligned;
+ compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml;
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 4e4f9b2f7fe5..18f90d80b9a1 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -20,10 +20,16 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
import android.net.DhcpInfo;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.net.wifi.ScanSettings;
import android.net.wifi.WifiChannel;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -38,6 +44,7 @@ import android.util.SparseArray;
import java.net.InetAddress;
import java.util.concurrent.CountDownLatch;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
@@ -568,6 +575,7 @@ public class WifiManager {
private Context mContext;
IWifiManager mService;
+ private final int mTargetSdkVersion;
private static final int INVALID_KEY = 0;
private static int sListenerKey = 1;
@@ -576,11 +584,17 @@ public class WifiManager {
private static AsyncChannel sAsyncChannel;
private static CountDownLatch sConnected;
+ private static ConnectivityManager sCM;
private static final Object sThreadRefLock = new Object();
private static int sThreadRefCount;
private static HandlerThread sHandlerThread;
+ @GuardedBy("sCM")
+ // TODO: Introduce refcounting and make this a per-process static callback, instead of a
+ // per-WifiManager callback.
+ private PinningNetworkCallback mNetworkCallback;
+
/**
* Create a new WifiManager instance.
* Applications will almost always want to use
@@ -594,6 +608,7 @@ public class WifiManager {
public WifiManager(Context context, IWifiManager service) {
mContext = context;
mService = service;
+ mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
init();
}
@@ -740,6 +755,20 @@ public class WifiManager {
* networks are disabled, and an attempt to connect to the selected
* network is initiated. This may result in the asynchronous delivery
* of state change events.
+ * <p>
+ * <b>Note:</b> If an application's target SDK version is
+ * {@link android.os.Build.VERSION_CODES#MNC} or newer, network
+ * communication may not use Wi-Fi even if Wi-Fi is connected; traffic may
+ * instead be sent through another network, such as cellular data,
+ * Bluetooth tethering, or Ethernet. For example, traffic will never use a
+ * Wi-Fi network that does not provide Internet access (e.g. a wireless
+ * printer), if another network that does offer Internet access (e.g.
+ * cellular data) is available. Applications that need to ensure that their
+ * network traffic uses Wi-Fi should use APIs such as
+ * {@link Network#bindSocket(java.net.Socket)},
+ * {@link Network#openConnection(java.net.URL)}, or
+ * {@link ConnectivityManager#bindProcessToNetwork} to do so.
+ *
* @param netId the ID of the network in the list of configured networks
* @param disableOthers if true, disable all other networks. The way to
* select a particular network to connect to is specify {@code true}
@@ -747,11 +776,23 @@ public class WifiManager {
* @return {@code true} if the operation succeeded
*/
public boolean enableNetwork(int netId, boolean disableOthers) {
+ final boolean pin = disableOthers && mTargetSdkVersion < Build.VERSION_CODES.MNC;
+ if (pin) {
+ registerPinningNetworkCallback();
+ }
+
+ boolean success;
try {
- return mService.enableNetwork(netId, disableOthers);
+ success = mService.enableNetwork(netId, disableOthers);
} catch (RemoteException e) {
- return false;
+ success = false;
+ }
+
+ if (pin && !success) {
+ unregisterPinningNetworkCallback();
}
+
+ return success;
}
/**
@@ -1951,6 +1992,92 @@ public class WifiManager {
"No permission to access and change wifi or a bad initialization");
}
+ private void initConnectivityManager() {
+ // TODO: what happens if an app calls a WifiManager API before ConnectivityManager is
+ // registered? Can we fix this by starting ConnectivityService before WifiService?
+ if (sCM == null) {
+ sCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (sCM == null) {
+ throw new IllegalStateException("Bad luck, ConnectivityService not started.");
+ }
+ }
+ }
+
+ /**
+ * A NetworkCallback that pins the process to the first wifi network to connect.
+ *
+ * We use this to maintain compatibility with pre-M apps that call WifiManager.enableNetwork()
+ * to connect to a Wi-Fi network that has no Internet access, and then assume that they will be
+ * able to use that network because it's the system default.
+ *
+ * In order to maintain compatibility with apps that call setProcessDefaultNetwork themselves,
+ * we try not to set the default network unless they have already done so, and we try not to
+ * clear the default network unless we set it ourselves.
+ *
+ * This should maintain behaviour that's compatible with L, which would pin the whole system to
+ * any wifi network that was created via enableNetwork(..., true) until that network
+ * disconnected.
+ *
+ * Note that while this hack allows network traffic to flow, it is quite limited. For example:
+ *
+ * 1. setProcessDefaultNetwork only affects this process, so:
+ * - Any subprocesses spawned by this process will not be pinned to Wi-Fi.
+ * - If this app relies on any other apps on the device also being on Wi-Fi, that won't work
+ * either, because other apps on the device will not be pinned.
+ * 2. The behaviour of other APIs is not modified. For example:
+ * - getActiveNetworkInfo will return the system default network, not Wi-Fi.
+ * - There will be no CONNECTIVITY_ACTION broadcasts about TYPE_WIFI.
+ * - getProcessDefaultNetwork will not return null, so if any apps are relying on that, they
+ * will be surprised as well.
+ */
+ private class PinningNetworkCallback extends NetworkCallback {
+ private Network mPinnedNetwork;
+
+ @Override
+ public void onPreCheck(Network network) {
+ if (sCM.getProcessDefaultNetwork() == null && mPinnedNetwork == null) {
+ sCM.setProcessDefaultNetwork(network);
+ mPinnedNetwork = network;
+ Log.d(TAG, "Wifi alternate reality enabled on network " + network);
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ if (network.equals(mPinnedNetwork) && network.equals(sCM.getProcessDefaultNetwork())) {
+ sCM.setProcessDefaultNetwork(null);
+ Log.d(TAG, "Wifi alternate reality disabled on network " + network);
+ mPinnedNetwork = null;
+ unregisterPinningNetworkCallback();
+ }
+ }
+ }
+
+ private void registerPinningNetworkCallback() {
+ initConnectivityManager();
+ synchronized (sCM) {
+ if (mNetworkCallback == null) {
+ // TODO: clear all capabilities.
+ NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build();
+ mNetworkCallback = new PinningNetworkCallback();
+ sCM.registerNetworkCallback(request, mNetworkCallback);
+ }
+ }
+ }
+
+ private void unregisterPinningNetworkCallback() {
+ initConnectivityManager();
+ synchronized (sCM) {
+ if (mNetworkCallback != null) {
+ sCM.unregisterNetworkCallback(mNetworkCallback);
+ mNetworkCallback = null;
+ }
+ }
+ }
+
/**
* Connect to a network with the given configuration. The network also
* gets added to the supplicant configuration.