diff options
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—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, ®ion); + NinePatch_Draw(NULL, bounds, bitmap, *chunk, NULL, ®ion); 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. |