Merge "Clarify the API documentation for WebSQL database." into jb-mr2-dev
diff --git a/Android.mk b/Android.mk
index b9cd7bf..5dc3523 100644
--- a/Android.mk
+++ b/Android.mk
@@ -102,6 +102,9 @@
core/java/android/bluetooth/IBluetoothManagerCallback.aidl \
core/java/android/bluetooth/IBluetoothPbap.aidl \
core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \
+ core/java/android/bluetooth/IBluetoothGatt.aidl \
+ core/java/android/bluetooth/IBluetoothGattCallback.aidl \
+ core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \
core/java/android/content/IClipboard.aidl \
core/java/android/content/IContentService.aidl \
core/java/android/content/IIntentReceiver.aidl \
diff --git a/api/current.txt b/api/current.txt
index a89c30a..a4b4992 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5917,6 +5917,7 @@
field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
+ field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
field public static final java.lang.String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -27456,7 +27457,7 @@
method public java.lang.String getUrl();
}
- public class WebIconDatabase {
+ public deprecated class WebIconDatabase {
method public void close();
method public static android.webkit.WebIconDatabase getInstance();
method public void open(java.lang.String);
@@ -27466,7 +27467,7 @@
method public void retainIconForPageUrl(java.lang.String);
}
- public static abstract interface WebIconDatabase.IconListener {
+ public static abstract deprecated interface WebIconDatabase.IconListener {
method public abstract void onReceivedIcon(java.lang.String, android.graphics.Bitmap);
}
@@ -27505,18 +27506,18 @@
method public synchronized boolean getJavaScriptCanOpenWindowsAutomatically();
method public synchronized boolean getJavaScriptEnabled();
method public synchronized android.webkit.WebSettings.LayoutAlgorithm getLayoutAlgorithm();
- method public boolean getLightTouchEnabled();
+ method public deprecated boolean getLightTouchEnabled();
method public boolean getLoadWithOverviewMode();
method public synchronized boolean getLoadsImagesAutomatically();
method public boolean getMediaPlaybackRequiresUserGesture();
method public synchronized int getMinimumFontSize();
method public synchronized int getMinimumLogicalFontSize();
- method public synchronized android.webkit.WebSettings.PluginState getPluginState();
+ method public deprecated synchronized android.webkit.WebSettings.PluginState getPluginState();
method public deprecated synchronized boolean getPluginsEnabled();
method public deprecated synchronized java.lang.String getPluginsPath();
method public synchronized java.lang.String getSansSerifFontFamily();
method public boolean getSaveFormData();
- method public boolean getSavePassword();
+ method public deprecated boolean getSavePassword();
method public synchronized java.lang.String getSerifFontFamily();
method public synchronized java.lang.String getStandardFontFamily();
method public deprecated synchronized android.webkit.WebSettings.TextSize getTextSize();
@@ -27528,7 +27529,7 @@
method public abstract void setAllowFileAccessFromFileURLs(boolean);
method public abstract void setAllowUniversalAccessFromFileURLs(boolean);
method public synchronized void setAppCacheEnabled(boolean);
- method public synchronized void setAppCacheMaxSize(long);
+ method public deprecated synchronized void setAppCacheMaxSize(long);
method public synchronized void setAppCachePath(java.lang.String);
method public synchronized void setBlockNetworkImage(boolean);
method public synchronized void setBlockNetworkLoads(boolean);
@@ -27551,20 +27552,20 @@
method public synchronized void setJavaScriptCanOpenWindowsAutomatically(boolean);
method public synchronized void setJavaScriptEnabled(boolean);
method public synchronized void setLayoutAlgorithm(android.webkit.WebSettings.LayoutAlgorithm);
- method public void setLightTouchEnabled(boolean);
+ method public deprecated void setLightTouchEnabled(boolean);
method public void setLoadWithOverviewMode(boolean);
method public synchronized void setLoadsImagesAutomatically(boolean);
method public void setMediaPlaybackRequiresUserGesture(boolean);
method public synchronized void setMinimumFontSize(int);
method public synchronized void setMinimumLogicalFontSize(int);
method public void setNeedInitialFocus(boolean);
- method public synchronized void setPluginState(android.webkit.WebSettings.PluginState);
+ method public deprecated synchronized void setPluginState(android.webkit.WebSettings.PluginState);
method public deprecated synchronized void setPluginsEnabled(boolean);
method public deprecated synchronized void setPluginsPath(java.lang.String);
- method public synchronized void setRenderPriority(android.webkit.WebSettings.RenderPriority);
+ method public deprecated synchronized void setRenderPriority(android.webkit.WebSettings.RenderPriority);
method public synchronized void setSansSerifFontFamily(java.lang.String);
method public void setSaveFormData(boolean);
- method public void setSavePassword(boolean);
+ method public deprecated void setSavePassword(boolean);
method public synchronized void setSerifFontFamily(java.lang.String);
method public synchronized void setStandardFontFamily(java.lang.String);
method public synchronized void setSupportMultipleWindows(boolean);
@@ -27631,7 +27632,7 @@
method public void getOrigins(android.webkit.ValueCallback<java.util.Map>);
method public void getQuotaForOrigin(java.lang.String, android.webkit.ValueCallback<java.lang.Long>);
method public void getUsageForOrigin(java.lang.String, android.webkit.ValueCallback<java.lang.Long>);
- method public void setQuotaForOrigin(java.lang.String, long);
+ method public deprecated void setQuotaForOrigin(java.lang.String, long);
}
public static class WebStorage.Origin {
@@ -27674,7 +27675,7 @@
method public void clearHistory();
method public void clearMatches();
method public void clearSslPreferences();
- method public void clearView();
+ method public deprecated void clearView();
method public android.webkit.WebBackForwardList copyBackForwardList();
method public void destroy();
method public void documentHasImages(android.os.Message);
@@ -27721,7 +27722,7 @@
method public void requestImageRef(android.os.Message);
method public android.webkit.WebBackForwardList restoreState(android.os.Bundle);
method public void resumeTimers();
- method public void savePassword(java.lang.String, java.lang.String, java.lang.String);
+ method public deprecated void savePassword(java.lang.String, java.lang.String, java.lang.String);
method public android.webkit.WebBackForwardList saveState(android.os.Bundle);
method public void saveWebArchive(java.lang.String);
method public void saveWebArchive(java.lang.String, boolean, android.webkit.ValueCallback<java.lang.String>);
@@ -27737,7 +27738,7 @@
method public void setVerticalScrollbarOverlay(boolean);
method public void setWebChromeClient(android.webkit.WebChromeClient);
method public void setWebViewClient(android.webkit.WebViewClient);
- method public boolean showFindDialog(java.lang.String, boolean);
+ method public deprecated boolean showFindDialog(java.lang.String, boolean);
method public void stopLoading();
method public boolean zoomIn();
method public boolean zoomOut();
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
old mode 100755
new mode 100644
index 6367e16..b00bf09
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1136,8 +1136,8 @@
/**
* Get the profile proxy object associated with the profile.
*
- * <p>Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET} or
- * {@link BluetoothProfile#A2DP}. Clients must implements
+ * <p>Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET},
+ * or {@link BluetoothProfile#A2DP}. Clients must implement
* {@link BluetoothProfile.ServiceListener} to get notified of
* the connection status and to get the proxy object.
*
@@ -1166,6 +1166,12 @@
} else if (profile == BluetoothProfile.HEALTH) {
BluetoothHealth health = new BluetoothHealth(context, listener);
return true;
+ } else if (profile == BluetoothProfile.GATT) {
+ BluetoothGatt gatt = new BluetoothGatt(context, listener);
+ return true;
+ } else if (profile == BluetoothProfile.GATT_SERVER) {
+ BluetoothGattServer gattServer = new BluetoothGattServer(context, listener);
+ return true;
} else {
return false;
}
@@ -1206,6 +1212,14 @@
BluetoothHealth health = (BluetoothHealth)proxy;
health.close();
break;
+ case BluetoothProfile.GATT:
+ BluetoothGatt gatt = (BluetoothGatt)proxy;
+ gatt.close();
+ break;
+ case BluetoothProfile.GATT_SERVER:
+ BluetoothGattServer gattServer = (BluetoothGattServer)proxy;
+ gattServer.close();
+ break;
}
}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
new file mode 100644
index 0000000..1e12025
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -0,0 +1,1309 @@
+/*
+ * 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.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Public API for the Bluetooth Gatt Profile.
+ *
+ * <p>This class provides Bluetooth Gatt functionality to enable communication
+ * with Bluetooth Smart or Smart Ready devices.
+ *
+ * <p>BluetoothGatt is a proxy object for controlling the Bluetooth Service
+ * via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the
+ * BluetoothGatt proxy object.
+ *
+ * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback}
+ * and call {@link #registerApp} to register your application. Gatt capable
+ * devices can be discovered using the {@link #startScan} function or the
+ * regular Bluetooth device discovery process.
+ * @hide
+ */
+public final class BluetoothGatt implements BluetoothProfile {
+ private static final String TAG = "BluetoothGatt";
+ private static final boolean DBG = true;
+
+ private Context mContext;
+ private ServiceListener mServiceListener;
+ private BluetoothAdapter mAdapter;
+ private IBluetoothGatt mService;
+ private BluetoothGattCallback mCallback;
+ private int mClientIf;
+ private boolean mAuthRetry = false;
+
+ private List<BluetoothGattService> mServices;
+
+ /** A Gatt operation completed successfully */
+ public static final int GATT_SUCCESS = 0;
+
+ /** Gatt read operation is not permitted */
+ public static final int GATT_READ_NOT_PERMITTED = 0x2;
+
+ /** Gatt write operation is not permitted */
+ public static final int GATT_WRITE_NOT_PERMITTED = 0x3;
+
+ /** Insufficient authentication for a given operation */
+ public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5;
+
+ /** The given request is not supported */
+ public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6;
+
+ /** Insufficient encryption for a given operation */
+ public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf;
+
+ /** A read or write operation was requested with an invalid offset */
+ public static final int GATT_INVALID_OFFSET = 0x7;
+
+ /** A write operation exceeds the maximum length of the attribute */
+ public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd;
+
+ /**
+ * No authentication required.
+ * @hide
+ */
+ /*package*/ static final int AUTHENTICATION_NONE = 0;
+
+ /**
+ * Authentication requested; no man-in-the-middle protection required.
+ * @hide
+ */
+ /*package*/ static final int AUTHENTICATION_NO_MITM = 1;
+
+ /**
+ * Authentication with man-in-the-middle protection requested.
+ * @hide
+ */
+ /*package*/ static final int AUTHENTICATION_MITM = 2;
+
+ /**
+ * Bluetooth state change handlers
+ */
+ private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ if (DBG) Log.d(TAG,"Unbinding service...");
+ synchronized (mConnection) {
+ mService = null;
+ mContext.unbindService(mConnection);
+ }
+ } else {
+ synchronized (mConnection) {
+ if (mService == null) {
+ if (DBG) Log.d(TAG,"Binding service...");
+ if (!mContext.bindService(new Intent(IBluetoothGatt.class.getName()),
+ mConnection, 0)) {
+ Log.e(TAG, "Could not bind to Bluetooth GATT Service");
+ }
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Service binder handling
+ */
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (DBG) Log.d(TAG, "Proxy object connected");
+ mService = IBluetoothGatt.Stub.asInterface(service);
+ ServiceListener serviceListener = mServiceListener;
+ if (serviceListener != null) {
+ serviceListener.onServiceConnected(BluetoothProfile.GATT, BluetoothGatt.this);
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) Log.d(TAG, "Proxy object disconnected");
+ mService = null;
+ ServiceListener serviceListener = mServiceListener;
+ if (serviceListener != null) {
+ serviceListener.onServiceDisconnected(BluetoothProfile.GATT);
+ }
+ }
+ };
+
+ /**
+ * Bluetooth GATT interface callbacks
+ */
+ private final IBluetoothGattCallback mBluetoothGattCallback =
+ new IBluetoothGattCallback.Stub() {
+ /**
+ * Application interface registered - app is ready to go
+ * @hide
+ */
+ public void onClientRegistered(int status, int clientIf) {
+ if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status
+ + " clientIf=" + clientIf);
+ mClientIf = clientIf;
+ try {
+ mCallback.onAppRegistered(status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Client connection state changed
+ * @hide
+ */
+ public void onClientConnectionState(int status, int clientIf,
+ boolean connected, String address) {
+ if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
+ + " clientIf=" + clientIf + " device=" + address);
+ try {
+ mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
+ connected ? BluetoothProfile.STATE_CONNECTED
+ : BluetoothProfile.STATE_DISCONNECTED);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Callback reporting an LE scan result.
+ * @hide
+ */
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
+
+ try {
+ mCallback.onScanResult(mAdapter.getRemoteDevice(address), rssi, advData);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * A new GATT service has been discovered.
+ * The service is added to the internal list and the search
+ * continues.
+ * @hide
+ */
+ public void onGetService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid) {
+ if (DBG) Log.d(TAG, "onGetService() - Device=" + address + " UUID=" + srvcUuid);
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ mServices.add(new BluetoothGattService(device, srvcUuid.getUuid(),
+ srvcInstId, srvcType));
+ }
+
+ /**
+ * An included service has been found durig GATT discovery.
+ * The included service is added to the respective parent.
+ * @hide
+ */
+ public void onGetIncludedService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int inclSrvcType, int inclSrvcInstId,
+ ParcelUuid inclSrvcUuid) {
+ if (DBG) Log.d(TAG, "onGetIncludedService() - Device=" + address
+ + " UUID=" + srvcUuid + " Included=" + inclSrvcUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(device,
+ srvcUuid.getUuid(), srvcInstId, srvcType);
+ BluetoothGattService includedService = getService(device,
+ inclSrvcUuid.getUuid(), inclSrvcInstId, inclSrvcType);
+
+ if (service != null && includedService != null) {
+ service.addIncludedService(includedService);
+ }
+ }
+
+ /**
+ * A new GATT characteristic has been discovered.
+ * Add the new characteristic to the relevant service and continue
+ * the remote device inspection.
+ * @hide
+ */
+ public void onGetCharacteristic(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int charProps) {
+ if (DBG) Log.d(TAG, "onGetCharacteristic() - Device=" + address + " UUID=" +
+ charUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service != null) {
+ service.addCharacteristic(new BluetoothGattCharacteristic(
+ service, charUuid.getUuid(), charInstId, charProps, 0));
+ }
+ }
+
+ /**
+ * A new GATT descriptor has been discovered.
+ * Finally, add the descriptor to the related characteristic.
+ * This should conclude the remote device update.
+ * @hide
+ */
+ public void onGetDescriptor(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ ParcelUuid descUuid) {
+ if (DBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid());
+ if (characteristic == null) return;
+
+ characteristic.addDescriptor(new BluetoothGattDescriptor(
+ characteristic, descUuid.getUuid(), 0));
+ }
+
+ /**
+ * Remote search has been completed.
+ * The internal object structure should now reflect the state
+ * of the remote device database. Let the application know that
+ * we are done at this point.
+ * @hide
+ */
+ public void onSearchComplete(String address, int status) {
+ if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ try {
+ mCallback.onServicesDiscovered(device, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote characteristic has been read.
+ * Updates the internal value.
+ * @hide
+ */
+ public void onCharacteristicRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid, byte[] value) {
+ if (DBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
+ + " UUID=" + charUuid + " Status=" + status);
+
+ if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+ || status == GATT_INSUFFICIENT_ENCRYPTION)
+ && mAuthRetry == false) {
+ try {
+ mAuthRetry = true;
+ mService.readCharacteristic(mClientIf, address,
+ srvcType, srvcInstId, srvcUuid,
+ charInstId, charUuid, AUTHENTICATION_MITM);
+ return;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ mAuthRetry = false;
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ if (status == 0) characteristic.setValue(value);
+
+ try {
+ mCallback.onCharacteristicRead(characteristic, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Characteristic has been written to the remote device.
+ * Let the app know how we did...
+ * @hide
+ */
+ public void onCharacteristicWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid) {
+ if (DBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
+ + " UUID=" + charUuid + " Status=" + status);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+ || status == GATT_INSUFFICIENT_ENCRYPTION)
+ && mAuthRetry == false) {
+ try {
+ mAuthRetry = true;
+ mService.writeCharacteristic(mClientIf, address,
+ srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+ characteristic.getWriteType(), AUTHENTICATION_MITM,
+ characteristic.getValue());
+ return;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ mAuthRetry = false;
+
+ try {
+ mCallback.onCharacteristicWrite(characteristic, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote characteristic has been updated.
+ * Updates the internal value.
+ * @hide
+ */
+ public void onNotify(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ byte[] value) {
+ if (DBG) Log.d(TAG, "onNotify() - Device=" + address + " UUID=" + charUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ characteristic.setValue(value);
+
+ try {
+ mCallback.onCharacteristicChanged(characteristic);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Descriptor has been read.
+ * @hide
+ */
+ public void onDescriptorRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ ParcelUuid descrUuid, byte[] value) {
+ if (DBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ descrUuid.getUuid());
+ if (descriptor == null) return;
+
+ if (status == 0) descriptor.setValue(value);
+
+ if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+ || status == GATT_INSUFFICIENT_ENCRYPTION)
+ && mAuthRetry == false) {
+ try {
+ mAuthRetry = true;
+ mService.readDescriptor(mClientIf, address,
+ srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+ descrUuid, AUTHENTICATION_MITM);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ mAuthRetry = true;
+
+ try {
+ mCallback.onDescriptorRead(descriptor, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Descriptor write operation complete.
+ * @hide
+ */
+ public void onDescriptorWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ ParcelUuid descrUuid) {
+ if (DBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ descrUuid.getUuid());
+ if (descriptor == null) return;
+
+ if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+ || status == GATT_INSUFFICIENT_ENCRYPTION)
+ && mAuthRetry == false) {
+ try {
+ mAuthRetry = true;
+ mService.writeDescriptor(mClientIf, address,
+ srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+ descrUuid, characteristic.getWriteType(),
+ AUTHENTICATION_MITM, descriptor.getValue());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ mAuthRetry = false;
+
+ try {
+ mCallback.onDescriptorWrite(descriptor, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Prepared write transaction completed (or aborted)
+ * @hide
+ */
+ public void onExecuteWrite(String address, int status) {
+ if (DBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
+ + " status=" + status);
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ try {
+ mCallback.onReliableWriteCompleted(device, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote device RSSI has been read
+ * @hide
+ */
+ public void onReadRemoteRssi(String address, int rssi, int status) {
+ if (DBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
+ " rssi=" + rssi + " status=" + status);
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ try {
+ mCallback.onReadRemoteRssi(device, rssi, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+ };
+
+ /**
+ * Create a BluetoothGatt proxy object.
+ */
+ /*package*/ BluetoothGatt(Context context, ServiceListener l) {
+ mContext = context;
+ mServiceListener = l;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mServices = new ArrayList<BluetoothGattService>();
+
+ IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+ if (b != null) {
+ IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to register BluetoothStateChangeCallback", re);
+ }
+ } else {
+ Log.e(TAG, "Unable to get BluetoothManager interface.");
+ throw new RuntimeException("BluetoothManager inactive");
+ }
+
+ //Bind to the service only if the Bluetooth is ON
+ if(mAdapter.isEnabled()){
+ if (!context.bindService(new Intent(IBluetoothGatt.class.getName()), mConnection, 0)) {
+ Log.e(TAG, "Could not bind to Bluetooth Gatt Service");
+ }
+ }
+ }
+
+ /**
+ * Close the connection to the gatt service.
+ */
+ /*package*/ void close() {
+ if (DBG) Log.d(TAG, "close()");
+
+ unregisterApp();
+ mServiceListener = null;
+
+ IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+ if (b != null) {
+ IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+ try {
+ mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to unregister BluetoothStateChangeCallback", re);
+ }
+ }
+
+ synchronized (mConnection) {
+ if (mService != null) {
+ mService = null;
+ mContext.unbindService(mConnection);
+ }
+ }
+ }
+
+ /**
+ * Returns a service by UUID, instance and type.
+ * @hide
+ */
+ /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
+ int instanceId, int type) {
+ for(BluetoothGattService svc : mServices) {
+ if (svc.getDevice().equals(device) &&
+ svc.getType() == type &&
+ svc.getInstanceId() == instanceId &&
+ svc.getUuid().equals(uuid)) {
+ return svc;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Register an application callback to start using Gatt.
+ *
+ * <p>This is an asynchronous call. The callback is used to notify
+ * success or failure if the function returns true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param callback Gatt callback handler that will receive asynchronous
+ * callbacks.
+ * @return true, if application was successfully registered.
+ */
+ public boolean registerApp(BluetoothGattCallback callback) {
+ if (DBG) Log.d(TAG, "registerApp()");
+ if (mService == null) return false;
+
+ mCallback = callback;
+ UUID uuid = UUID.randomUUID();
+ if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
+
+ try {
+ mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Unregister the current application and callbacks.
+ */
+ public void unregisterApp() {
+ if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
+ if (mService == null || mClientIf == 0) return;
+
+ try {
+ mCallback = null;
+ mService.unregisterClient(mClientIf);
+ mClientIf = 0;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Starts a scan for Bluetooth LE devices.
+ *
+ * <p>Results of the scan are reported using the
+ * {@link BluetoothGattCallback#onScanResult} callback.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return true, if the scan was started successfully
+ */
+ public boolean startScan() {
+ if (DBG) Log.d(TAG, "startScan()");
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.startScan(mClientIf, false);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Starts a scan for Bluetooth LE devices, looking for devices that
+ * advertise given services.
+ *
+ * <p>Devices which advertise all specified services are reported using the
+ * {@link BluetoothGattCallback#onScanResult} callback.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param serviceUuids Array of services to look for
+ * @return true, if the scan was started successfully
+ */
+ public boolean startScan(UUID[] serviceUuids) {
+ if (DBG) Log.d(TAG, "startScan() - with UUIDs");
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ ParcelUuid[] uuids = new ParcelUuid[serviceUuids.length];
+ for(int i = 0; i != uuids.length; ++i) {
+ uuids[i] = new ParcelUuid(serviceUuids[i]);
+ }
+ mService.startScanWithUuids(mClientIf, false, uuids);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Stops an ongoing Bluetooth LE device scan.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ */
+ public void stopScan() {
+ if (DBG) Log.d(TAG, "stopScan()");
+ if (mService == null || mClientIf == 0) return;
+
+ try {
+ mService.stopScan(mClientIf, false);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Initiate a connection to a Bluetooth Gatt capable device.
+ *
+ * <p>The connection may not be established right away, but will be
+ * completed when the remote device is available. A
+ * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+ * invoked when the connection state changes as a result of this function.
+ *
+ * <p>The autoConnect paramter determines whether to actively connect to
+ * the remote device, or rather passively scan and finalize the connection
+ * when the remote device is in range/available. Generally, the first ever
+ * connection to a device should be direct (autoConnect set to false) and
+ * subsequent connections to known devices should be invoked with the
+ * autoConnect parameter set to false.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device to connect to
+ * @param autoConnect Whether to directly connect to the remote device (false)
+ * or to automatically connect as soon as the remote
+ * device becomes available (true).
+ * @return true, if the connection attempt was initiated successfully
+ */
+ public boolean connect(BluetoothDevice device, boolean autoConnect) {
+ if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.clientConnect(mClientIf, device.getAddress(),
+ autoConnect ? false : true); // autoConnect is inverse of "isDirect"
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Disconnects an established connection, or cancels a connection attempt
+ * currently in progress.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device
+ */
+ public void cancelConnection(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "cancelOpen() - device: " + device.getAddress());
+ if (mService == null || mClientIf == 0) return;
+
+ try {
+ mService.clientDisconnect(mClientIf, device.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Discovers services offered by a remote device as well as their
+ * characteristics and descriptors.
+ *
+ * <p>This is an asynchronous operation. Once service discovery is completed,
+ * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
+ * triggered. If the discovery was successful, the remote services can be
+ * retrieved using the {@link #getServices} function.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device to explore
+ * @return true, if the remote service discovery has been started
+ */
+ public boolean discoverServices(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "discoverServices() - device: " + device.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ mServices.clear();
+
+ try {
+ mService.discoverServices(mClientIf, device.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a list of GATT services offered by the remote device.
+ *
+ * <p>This function requires that service discovery has been completed
+ * for the given device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device
+ * @return List of services on the remote device. Returns an empty list
+ * if service discovery has not yet been performed.
+ */
+ public List<BluetoothGattService> getServices(BluetoothDevice device) {
+ List<BluetoothGattService> result =
+ new ArrayList<BluetoothGattService>();
+
+ for (BluetoothGattService service : mServices) {
+ if (service.getDevice().equals(device)) {
+ result.add(service);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a {@link BluetoothGattService}, if the requested UUID is
+ * supported by the remote device.
+ *
+ * <p>This function requires that service discovery has been completed
+ * for the given device.
+ *
+ * <p>If multiple instances of the same service (as identified by UUID)
+ * exist, the first instance of the service is returned.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device
+ * @param uuid UUID of the requested service
+ * @return BluetoothGattService if supported, or null if the requested
+ * service is not offered by the remote device.
+ */
+ public BluetoothGattService getService(BluetoothDevice device, UUID uuid) {
+ for (BluetoothGattService service : mServices) {
+ if (service.getDevice().equals(device) &&
+ service.getUuid().equals(uuid)) {
+ return service;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Reads the requested characteristic from the associated remote device.
+ *
+ * <p>This is an asynchronous operation. The result of the read operation
+ * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
+ * callback.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic Characteristic to read from the remote device
+ * @return true, if the read operation was initiated successfully
+ */
+ public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
+ if ((characteristic.getProperties() &
+ BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;
+
+ if (DBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.readCharacteristic(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()), AUTHENTICATION_NONE);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Writes a given characteristic and it's values to the associated remote
+ * device.
+ *
+ * <p>Once the write operation has been completed, the
+ * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
+ * reporting the result of the operation.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic Characteristic to write on the remote device
+ * @return true, if the write operation was initiated successfully
+ */
+ public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+ if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
+ && (characteristic.getProperties() &
+ BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
+
+ if (DBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.writeCharacteristic(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()),
+ characteristic.getWriteType(), AUTHENTICATION_NONE,
+ characteristic.getValue());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Reads the value for a given descriptor from the associated remote device.
+ *
+ * <p>Once the read operation has been completed, the
+ * {@link BluetoothGattCallback#onDescriptorRead} callback is
+ * triggered, signaling the result of the operation.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param descriptor Descriptor value to read from the remote device
+ * @return true, if the read operation was initiated successfully
+ */
+ public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
+ if (DBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
+ if (characteristic == null) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.readDescriptor(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()),
+ new ParcelUuid(descriptor.getUuid()), AUTHENTICATION_NONE);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Write the value of a given descriptor to the associated remote device.
+ *
+ * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
+ * triggered to report the result of the write operation.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param descriptor Descriptor to write to the associated remote device
+ * @return true, if the write operation was initiated successfully
+ */
+ public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
+ if (DBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
+ if (characteristic == null) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.writeDescriptor(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()),
+ new ParcelUuid(descriptor.getUuid()),
+ characteristic.getWriteType(), AUTHENTICATION_NONE,
+ descriptor.getValue());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Initiates a reliable write transaction for a given remote device.
+ *
+ * <p>Once a reliable write transaction has been initiated, all calls
+ * to {@link #writeCharacteristic} are sent to the remote device for
+ * verification and queued up for atomic execution. The application will
+ * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback
+ * in response to every {@link #writeCharacteristic} call and is responsible
+ * for verifying if the value has been transmitted accurately.
+ *
+ * <p>After all characteristics have been queued up and verified,
+ * {@link #executeReliableWrite} will execute all writes. If a characteristic
+ * was not written correctly, calling {@link #abortReliableWrite} will
+ * cancel the current transaction without commiting any values on the
+ * remote device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device
+ * @return true, if the reliable write transaction has been initiated
+ */
+ public boolean beginReliableWrite(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "beginReliableWrite() - device: " + device.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.beginReliableWrite(mClientIf, device.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Executes a reliable write transaction for a given remote device.
+ *
+ * <p>This function will commit all queued up characteristic write
+ * operations for a given remote device.
+ *
+ * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
+ * invoked to indicate whether the transaction has been executed correctly.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device
+ * @return true, if the request to execute the transaction has been sent
+ */
+ public boolean executeReliableWrite(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "executeReliableWrite() - device: " + device.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.endReliableWrite(mClientIf, device.getAddress(), true);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Cancels a reliable write transaction for a given device.
+ *
+ * <p>Calling this function will discard all queued characteristic write
+ * operations for a given remote device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device
+ */
+ public void abortReliableWrite(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "abortReliableWrite() - device: " + device.getAddress());
+ if (mService == null || mClientIf == 0) return;
+
+ try {
+ mService.endReliableWrite(mClientIf, device.getAddress(), false);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Enable or disable notifications/indications for a given characteristic.
+ *
+ * <p>Once notifications are enabled for a characteristic, a
+ * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be
+ * triggered if the remote device indicates that the given characteristic
+ * has changed.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic The characteristic for which to enable notifications
+ * @param enable Set to true to enable notifications/indications
+ * @return true, if the requested notification status was set successfully
+ */
+ public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+ boolean enable) {
+ if (DBG) Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid()
+ + " enable: " + enable);
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.registerForNotification(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()),
+ enable);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Clears the internal cache and forces a refresh of the services from the
+ * remote device.
+ * @hide
+ */
+ public boolean refresh(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "refresh() - device: " + device.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.refreshDevice(mClientIf, device.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Read the RSSI for a connected remote device.
+ *
+ * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
+ * invoked when the RSSI value has been read.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device
+ * @return true, if the RSSI value has been requested successfully
+ */
+ public boolean readRemoteRssi(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "readRssi() - device: " + device.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.readRemoteRssi(mClientIf, device.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the current connection state of the profile.
+ *
+ * <p>This is not specific to any application configuration but represents
+ * the connection state of the local Bluetooth adapter for this profile.
+ * This can be used by applications like status bar which would just like
+ * to know the state of the local adapter.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote bluetooth device.
+ * @return State of the profile connection. One of
+ * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
+ */
+ @Override
+ public int getConnectionState(BluetoothDevice device) {
+ if (DBG) Log.d(TAG,"getConnectionState()");
+ if (mService == null) return STATE_DISCONNECTED;
+
+ List<BluetoothDevice> connectedDevices = getConnectedDevices();
+ for(BluetoothDevice connectedDevice : connectedDevices) {
+ if (device.equals(connectedDevice)) {
+ return STATE_CONNECTED;
+ }
+ }
+
+ return STATE_DISCONNECTED;
+ }
+
+ /**
+ * Get connected devices for the Gatt profile.
+ *
+ * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+ *
+ * <p>This is not specific to any application configuration but represents
+ * the connection state of the local Bluetooth adapter for this profile.
+ * This can be used by applications like status bar which would just like
+ * to know the state of the local adapter.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return List of devices. The list will be empty on error.
+ */
+ @Override
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (DBG) Log.d(TAG,"getConnectedDevices");
+
+ List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>();
+ if (mService == null) return connectedDevices;
+
+ try {
+ connectedDevices = mService.getDevicesMatchingConnectionStates(
+ new int[] { BluetoothProfile.STATE_CONNECTED });
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+
+ return connectedDevices;
+ }
+
+ /**
+ * Get a list of devices that match any of the given connection
+ * states.
+ *
+ * <p> If none of the devices match any of the given states,
+ * an empty list will be returned.
+ *
+ * <p>This is not specific to any application configuration but represents
+ * the connection state of the local Bluetooth adapter for this profile.
+ * This can be used by applications like status bar which would just like
+ * to know the state of the local adapter.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param states Array of states. States can be one of
+ * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
+ * @return List of devices. The list will be empty on error.
+ */
+ @Override
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (DBG) Log.d(TAG,"getDevicesMatchingConnectionStates");
+
+ List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ if (mService == null) return devices;
+
+ try {
+ devices = mService.getDevicesMatchingConnectionStates(states);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+
+ return devices;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
new file mode 100644
index 0000000..afa4539
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+import android.util.Log;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGatt} callbacks.
+ * @hide
+ */
+public abstract class BluetoothGattCallback {
+ /**
+ * Callback to inform change in registration state of the application.
+ *
+ * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the application
+ * was successfully registered.
+ */
+ public void onAppRegistered(int status) {
+ }
+
+ /**
+ * Callback reporting an LE device found during a device scan initiated
+ * by the {@link BluetoothGatt#startScan} function.
+ *
+ * @param device Identifies the remote device
+ * @param rssi The RSSI value for the remote device as reported by the
+ * Bluetooth hardware. 0 if no RSSI value is available.
+ * @param scanRecord The content of the advertisement record offered by
+ * the remote device.
+ */
+ public void onScanResult(BluetoothDevice device, int rssi, byte[] scanRecord) {
+ }
+
+ /**
+ * Callback indicating when a remote device has been connected or disconnected.
+ *
+ * @param device Remote device that has been connected or disconnected.
+ * @param status Status of the connect or disconnect operation.
+ * @param newState Returns the new connection state. Can be one of
+ * {@link BluetoothProfile#STATE_DISCONNECTED} or
+ * {@link BluetoothProfile#STATE_CONNECTED}
+ */
+ public void onConnectionStateChange(BluetoothDevice device, int status,
+ int newState) {
+ }
+
+ /**
+ * Callback invoked when the list of remote services, characteristics and
+ * descriptors for the remote device have been updated.
+ *
+ * @param device Remote device
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device
+ * has been explored successfully.
+ */
+ public void onServicesDiscovered(BluetoothDevice device, int status) {
+ }
+
+ /**
+ * Callback reporting the result of a characteristic read operation.
+ *
+ * @param characteristic Characteristic that was read from the associated
+ * remote device.
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
+ * was completed successfully.
+ */
+ public void onCharacteristicRead(BluetoothGattCharacteristic characteristic,
+ int status) {
+ }
+
+ /**
+ * Callback indicating the result of a characteristic write operation.
+ *
+ * <p>If this callback is invoked while a reliable write transaction is
+ * in progress, the value of the characteristic represents the value
+ * reported by the remote device. An application should compare this
+ * value to the desired value to be written. If the values don't match,
+ * the application must abort the reliable write transaction.
+ *
+ * @param characteristic Characteristic that was written to the associated
+ * remote device.
+ * @param status The result of the write operation
+ */
+ public void onCharacteristicWrite(BluetoothGattCharacteristic characteristic,
+ int status) {
+ }
+
+ /**
+ * Callback triggered as a result of a remote characteristic notification.
+ *
+ * @param characteristic Characteristic that has been updated as a result
+ * of a remote notification event.
+ */
+ public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic) {
+ }
+
+ /**
+ * Callback reporting the result of a descriptor read operation.
+ *
+ * @param descriptor Descriptor that was read from the associated
+ * remote device.
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
+ * was completed successfully
+ */
+ public void onDescriptorRead(BluetoothGattDescriptor descriptor,
+ int status) {
+ }
+
+ /**
+ * Callback indicating the result of a descriptor write operation.
+ *
+ * @param descriptor Descriptor that was writte to the associated
+ * remote device.
+ * @param status The result of the write operation
+ */
+ public void onDescriptorWrite(BluetoothGattDescriptor descriptor,
+ int status) {
+ }
+
+ /**
+ * Callback invoked when a reliable write transaction has been completed.
+ *
+ * @param device Remote device
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the reliable write
+ * transaction was executed successfully
+ */
+ public void onReliableWriteCompleted(BluetoothDevice device, int status) {
+ }
+
+ /**
+ * Callback reporting the RSSI for a remote device connection.
+ *
+ * This callback is triggered in response to the
+ * {@link BluetoothGatt#readRemoteRssi} function.
+ *
+ * @param device Identifies the remote device
+ * @param rssi The RSSI value for the remote device
+ * @param status 0 if the RSSI was read successfully
+ */
+ public void onReadRemoteRssi(BluetoothDevice device, int rssi, int status) {
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
new file mode 100644
index 0000000..18492ab
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -0,0 +1,670 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.IllegalFormatConversionException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth Gatt Characteristic
+ * @hide
+ */
+public class BluetoothGattCharacteristic {
+
+ /**
+ * Characteristic proprty: Characteristic is broadcastable.
+ */
+ public static final int PROPERTY_BROADCAST = 0x01;
+
+ /**
+ * Characteristic property: Characteristic is readable.
+ */
+ public static final int PROPERTY_READ = 0x02;
+
+ /**
+ * Characteristic property: Characteristic can be written without response.
+ */
+ public static final int PROPERTY_WRITE_NO_RESPONSE = 0x04;
+
+ /**
+ * Characteristic property: Characteristic can be written.
+ */
+ public static final int PROPERTY_WRITE = 0x08;
+
+ /**
+ * Characteristic property: Characteristic supports notification
+ */
+ public static final int PROPERTY_NOTIFY = 0x10;
+
+ /**
+ * Characteristic property: Characteristic supports indication
+ */
+ public static final int PROPERTY_INDICATE = 0x20;
+
+ /**
+ * Characteristic property: Characteristic supports write with signature
+ */
+ public static final int PROPERTY_SIGNED_WRITE = 0x40;
+
+ /**
+ * Characteristic property: Characteristic has extended properties
+ */
+ public static final int PROPERTY_EXTENDED_PROPS = 0x80;
+
+ /**
+ * Characteristic read permission
+ */
+ public static final int PERMISSION_READ = 0x01;
+
+ /**
+ * Characteristic permission: Allow encrypted read operations
+ */
+ public static final int PERMISSION_READ_ENCRYPTED = 0x02;
+
+ /**
+ * Characteristic permission: Allow reading with man-in-the-middle protection
+ */
+ public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
+
+ /**
+ * Characteristic write permission
+ */
+ public static final int PERMISSION_WRITE = 0x10;
+
+ /**
+ * Characteristic permission: Allow encrypted writes
+ */
+ public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
+
+ /**
+ * Characteristic permission: Allow encrypted writes with man-in-the-middle
+ * protection
+ */
+ public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
+
+ /**
+ * Characteristic permission: Allow signed write operations
+ */
+ public static final int PERMISSION_WRITE_SIGNED = 0x80;
+
+ /**
+ * Characteristic permission: Allow signed write operations with
+ * man-in-the-middle protection
+ */
+ public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
+
+ /**
+ * Write characteristic, requesting acknoledgement by the remote device
+ */
+ public static final int WRITE_TYPE_DEFAULT = 0x02;
+
+ /**
+ * Wrtite characteristic without requiring a response by the remote device
+ */
+ public static final int WRITE_TYPE_NO_RESPONSE = 0x01;
+
+ /**
+ * Write characteristic including and authenticated signature
+ */
+ public static final int WRITE_TYPE_SIGNED = 0x04;
+
+ /**
+ * Characteristic value format type uint8
+ */
+ public static final int FORMAT_UINT8 = 0x11;
+
+ /**
+ * Characteristic value format type uint16
+ */
+ public static final int FORMAT_UINT16 = 0x12;
+
+ /**
+ * Characteristic value format type uint32
+ */
+ public static final int FORMAT_UINT32 = 0x14;
+
+ /**
+ * Characteristic value format type sint8
+ */
+ public static final int FORMAT_SINT8 = 0x21;
+
+ /**
+ * Characteristic value format type sint16
+ */
+ public static final int FORMAT_SINT16 = 0x22;
+
+ /**
+ * Characteristic value format type sint32
+ */
+ public static final int FORMAT_SINT32 = 0x24;
+
+ /**
+ * Characteristic value format type sfloat (16-bit float)
+ */
+ public static final int FORMAT_SFLOAT = 0x32;
+
+ /**
+ * Characteristic value format type float (32-bit float)
+ */
+ public static final int FORMAT_FLOAT = 0x34;
+
+
+ /**
+ * The UUID of this characteristic.
+ * @hide
+ */
+ protected UUID mUuid;
+
+ /**
+ * Instance ID for this characteristic.
+ * @hide
+ */
+ protected int mInstance;
+
+ /**
+ * Characteristic properties.
+ * @hide
+ */
+ protected int mProperties;
+
+ /**
+ * Characteristic permissions.
+ * @hide
+ */
+ protected int mPermissions;
+
+ /**
+ * Key size (default = 16).
+ * @hide
+ */
+ protected int mKeySize = 16;
+
+ /**
+ * Write type for this characteristic.
+ * See WRITE_TYPE_* constants.
+ * @hide
+ */
+ protected int mWriteType;
+
+ /**
+ * Back-reference to the service this characteristic belongs to.
+ * @hide
+ */
+ protected BluetoothGattService mService;
+
+ /**
+ * The cached value of this characteristic.
+ * @hide
+ */
+ protected byte[] mValue;
+
+ /**
+ * List of descriptors included in this characteristic.
+ */
+ protected List<BluetoothGattDescriptor> mDescriptors;
+
+ /**
+ * Create a new BluetoothGattCharacteristic
+ * @hide
+ */
+ /*package*/ BluetoothGattCharacteristic(BluetoothGattService service,
+ UUID uuid, int instanceId,
+ int properties, int permissions) {
+ mUuid = uuid;
+ mInstance = instanceId;
+ mProperties = properties;
+ mPermissions = permissions;
+ mService = service;
+ mValue = null;
+ mDescriptors = new ArrayList<BluetoothGattDescriptor>();
+
+ if ((mProperties & PROPERTY_WRITE_NO_RESPONSE) != 0) {
+ mWriteType = WRITE_TYPE_NO_RESPONSE;
+ } else {
+ mWriteType = WRITE_TYPE_DEFAULT;
+ }
+ }
+
+ /**
+ * Returns the deisred key size.
+ * @hide
+ */
+ /*package*/ int getKeySize() {
+ return mKeySize;
+ }
+
+ /**
+ * Add a descriptor to this characteristic
+ * @hide
+ */
+ /*package*/ void addDescriptor(BluetoothGattDescriptor descriptor) {
+ mDescriptors.add(descriptor);
+ }
+
+ /**
+ * Returns the service this characteristic belongs to.
+ * @return The asscociated service
+ */
+ public BluetoothGattService getService() {
+ return mService;
+ }
+
+ /**
+ * Returns the UUID of this characteristic
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return UUID of this characteristic
+ */
+ public UUID getUuid() {
+ return mUuid;
+ }
+
+ /**
+ * Returns the instance ID for this characteristic.
+ *
+ * <p>If a remote device offers multiple characteristics with the same UUID,
+ * the instance ID is used to distuinguish between characteristics.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Instance ID of this characteristic
+ */
+ public int getInstanceId() {
+ return mInstance;
+ }
+
+ /**
+ * Returns the properties of this characteristic.
+ *
+ * <p>The properties contain a bit mask of property flags indicating
+ * the features of this characteristic.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Properties of this characteristic
+ */
+ public int getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Returns the permissions for this characteristic.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Permissions of this characteristic
+ */
+ public int getPermissions() {
+ return mPermissions;
+ }
+
+ /**
+ * Gets the write type for this characteristic.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Write type for this characteristic
+ */
+ public int getWriteType() {
+ return mWriteType;
+ }
+
+ /**
+ * Set the write type for this characteristic
+ *
+ * <p>Setting the write type of a characteristic determines how the
+ * {@link BluetoothGatt#writeCharacteristic} function write this
+ * characteristic.
+ *
+ * <p>The default write type for a characteristic is
+ * {@link #WRITE_TYPE_DEFAULT}.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param writeType The write type to for this characteristic. Can be one
+ * of:
+ * {@link #WRITE_TYPE_DEFAULT},
+ * {@link #WRITE_TYPE_NO_RESPONSE} or
+ * {@link #WRITE_TYPE_SIGNED}.
+ */
+ public void setWriteType(int writeType) {
+ mWriteType = writeType;
+ }
+
+ /**
+ * Returns a list of descriptors for this characteristic.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Descriptors for this characteristic
+ */
+ public List<BluetoothGattDescriptor> getDescriptors() {
+ return mDescriptors;
+ }
+
+ /**
+ * Returns a descriptor with a given UUID out of the list of
+ * descriptors for this characteristic.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Gatt descriptor object or null if no descriptor with the
+ * given UUID was found.
+ */
+ public BluetoothGattDescriptor getDescriptor(UUID uuid) {
+ for(BluetoothGattDescriptor descriptor : mDescriptors) {
+ if (descriptor.getUuid().equals(uuid)) {
+ return descriptor;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the stored value for this characteristic.
+ *
+ * <p>This function returns the stored value for this characteristic as
+ * retrieved by calling {@link BluetoothGatt#readCharacteristic}. To cached
+ * value of the characteristic is updated as a result of a read characteristic
+ * operation or if a characteristic update notification has been received.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Cached value of the characteristic
+ */
+ public byte[] getValue() {
+ return mValue;
+ }
+
+ /**
+ * Return the stored value of this characteristic.
+ *
+ * <p>The formatType parameter determines how the characteristic value
+ * is to be interpreted. For example, settting formatType to
+ * {@link #FORMAT_UINT16} specifies that the first two bytes of the
+ * characteristic value at the given offset are interpreted to generate the
+ * return value.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param formatType The format type used to interpret the characteristic
+ * value.
+ * @param offset Offset at which the integer value can be found.
+ * @return Cached value of the characteristic or null of offset exceeds
+ * value size.
+ */
+ public Integer getIntValue(int formatType, int offset) {
+ if ((offset + getTypeLen(formatType)) > mValue.length) return null;
+
+ switch (formatType) {
+ case FORMAT_UINT8:
+ return unsignedByteToInt(mValue[offset]);
+
+ case FORMAT_UINT16:
+ return unsignedBytesToInt(mValue[offset], mValue[offset+1]);
+
+ case FORMAT_UINT32:
+ return unsignedBytesToInt(mValue[offset], mValue[offset+1],
+ mValue[offset+2], mValue[offset+3]);
+ case FORMAT_SINT8:
+ return unsignedToSigned(unsignedByteToInt(mValue[offset]), 8);
+
+ case FORMAT_SINT16:
+ return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+ mValue[offset+1]), 16);
+
+ case FORMAT_SINT32:
+ return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+ mValue[offset+1], mValue[offset+2], mValue[offset+3]), 32);
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the stored value of this characteristic.
+ * <p>See {@link #getValue} for details.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param formatType The format type used to interpret the characteristic
+ * value.
+ * @param offset Offset at which the float value can be found.
+ * @return Cached value of the characteristic at a given offset or null
+ * if the requested offset exceeds the value size.
+ */
+ public Float getFloatValue(int formatType, int offset) {
+ if ((offset + getTypeLen(formatType)) > mValue.length) return null;
+
+ switch (formatType) {
+ case FORMAT_SFLOAT:
+ return bytesToFloat(mValue[offset], mValue[offset+1]);
+
+ case FORMAT_FLOAT:
+ return bytesToFloat(mValue[offset], mValue[offset+1],
+ mValue[offset+2], mValue[offset+3]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the stored value of this characteristic.
+ * <p>See {@link #getValue} for details.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ * @param offset Offset at which the string value can be found.
+ * @return Cached value of the characteristic
+ */
+ public String getStringValue(int offset) {
+ if (offset > mValue.length) return null;
+ byte[] strBytes = new byte[mValue.length - offset];
+ for (int i=0; i != (mValue.length-offset); ++i) strBytes[i] = mValue[offset+i];
+ return new String(strBytes);
+ }
+
+ /**
+ * Updates the locally stored value of this characteristic.
+ *
+ * <p>This function modifies the locally stored cached value of this
+ * characteristic. To send the value to the remote device, call
+ * {@link BluetoothGatt#writeCharacteristic} to send the value to the
+ * remote device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param value New value for this characteristic
+ * @return true if the locally stored value has been set, false if the
+ * requested value could not be stored locally.
+ */
+ public boolean setValue(byte[] value) {
+ mValue = value;
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this characteristic.
+ * <p>See {@link #setValue(byte[])} for details.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param value New value for this characteristic
+ * @param formatType Integer format type used to transform the value parameter
+ * @param offset Offset at which the value should be placed
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(int value, int formatType, int offset) {
+ int len = offset + getTypeLen(formatType);
+ if (mValue == null) mValue = new byte[len];
+ if (len > mValue.length) return false;
+
+ switch (formatType) {
+ case FORMAT_SINT8:
+ value = intToSignedBits(value, 8);
+ // Fall-through intended
+ case FORMAT_UINT8:
+ mValue[offset] = (byte)(value & 0xFF);
+ break;
+
+ case FORMAT_SINT16:
+ value = intToSignedBits(value, 16);
+ // Fall-through intended
+ case FORMAT_UINT16:
+ mValue[offset++] = (byte)(value & 0xFF);
+ mValue[offset] = (byte)((value >> 8) & 0xFF);
+ break;
+
+ case FORMAT_SINT32:
+ value = intToSignedBits(value, 32);
+ // Fall-through intended
+ case FORMAT_UINT32:
+ mValue[offset++] = (byte)(value & 0xFF);
+ mValue[offset++] = (byte)((value >> 8) & 0xFF);
+ mValue[offset] = (byte)((value >> 16) & 0xFF);
+ break;
+
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this characteristic.
+ * <p>See {@link #setValue(byte[])} for details.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ * @param mantissa Mantissa for this characteristic
+ * @param exponent exponent value for this characteristic
+ * @param formatType Float format type used to transform the value parameter
+ * @param offset Offset at which the value should be placed
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(int mantissa, int exponent, int formatType, int offset) {
+ int len = offset + getTypeLen(formatType);
+ if (mValue == null) mValue = new byte[len];
+ if (len > mValue.length) return false;
+
+ switch (formatType) {
+ case FORMAT_SFLOAT:
+ mantissa = intToSignedBits(mantissa, 12);
+ exponent = intToSignedBits(exponent, 4);
+ mValue[offset++] = (byte)(mantissa & 0xFF);
+ mValue[offset] = (byte)((mantissa >> 8) & 0x0F);
+ mValue[offset] += (byte)((exponent & 0x0F) << 4);
+ break;
+
+ case FORMAT_FLOAT:
+ mantissa = intToSignedBits(mantissa, 24);
+ exponent = intToSignedBits(exponent, 8);
+ mValue[offset++] = (byte)(mantissa & 0xFF);
+ mValue[offset++] = (byte)((mantissa >> 8) & 0xFF);
+ mValue[offset++] = (byte)((mantissa >> 16) & 0xFF);
+ mValue[offset] += (byte)(exponent & 0xFF);
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this characteristic.
+ * <p>See {@link #setValue(byte[])} for details.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ * @param value New value for this characteristic
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(String value) {
+ mValue = value.getBytes();
+ return true;
+ }
+
+ /**
+ * Returns the size of a give value type.
+ * @hide
+ */
+ private int getTypeLen(int formatType) {
+ return formatType & 0xF;
+ }
+
+ /**
+ * Convert a signed byte to an unsigned int.
+ * @hide
+ */
+ private int unsignedByteToInt(byte b) {
+ return b & 0xFF;
+ }
+
+ /**
+ * Convert signed bytes to a 16-bit unsigned int.
+ * @hide
+ */
+ private int unsignedBytesToInt(byte b0, byte b1) {
+ return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8));
+ }
+
+ /**
+ * Convert signed bytes to a 32-bit unsigned int.
+ * @hide
+ */
+ private int unsignedBytesToInt(byte b0, byte b1, byte b2, byte b3) {
+ return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8))
+ + (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24);
+ }
+
+ /**
+ * Convert signed bytes to a 16-bit short float value.
+ * @hide
+ */
+ private float bytesToFloat(byte b0, byte b1) {
+ int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+ + ((unsignedByteToInt(b1) & 0x0F) << 8), 12);
+ int exponent = unsignedToSigned(unsignedByteToInt(b1) >> 4, 4);
+ return (float)(mantissa * Math.pow(10, exponent));
+ }
+
+ /**
+ * Convert signed bytes to a 32-bit short float value.
+ * @hide
+ */
+ private float bytesToFloat(byte b0, byte b1, byte b2, byte b3) {
+ int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+ + (unsignedByteToInt(b1) << 8)
+ + (unsignedByteToInt(b2) << 16), 24);
+ return (float)(mantissa * Math.pow(10, b3));
+ }
+
+ /**
+ * Convert an unsigned integer value to a two's-complement encoded
+ * signed value.
+ * @hide
+ */
+ private int unsignedToSigned(int unsigned, int size) {
+ if ((unsigned & (1 << size-1)) != 0) {
+ unsigned = -1 * ((1 << size-1) - (unsigned & ((1 << size-1) - 1)));
+ }
+ return unsigned;
+ }
+
+ /**
+ * Convert an integer into the signed bits of a given length.
+ * @hide
+ */
+ private int intToSignedBits(int i, int size) {
+ if (i < 0) {
+ i = (1 << size-1) + (i & ((1 << size-1) - 1));
+ }
+ return i;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
new file mode 100644
index 0000000..ba1f28a
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -0,0 +1,185 @@
+/*
+ * 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 java.util.UUID;
+
+/**
+ * Represents a Bluetooth Gatt Descriptor
+ * @hide
+ */
+public class BluetoothGattDescriptor {
+
+ /**
+ * Value used to enable notification for a client configuration descriptor
+ */
+ public static final byte[] ENABLE_NOTIFICATION_VALUE = {0x01, 0x00};
+
+ /**
+ * Value used to enable indication for a client configuration descriptor
+ */
+ public static final byte[] ENABLE_INDICATION_VALUE = {0x02, 0x00};
+
+ /**
+ * Value used to disable notifications or indicatinos
+ */
+ public static final byte[] DISABLE_NOTIFICATION_VALUE = {0x00, 0x00};
+
+ /**
+ * Descriptor read permission
+ */
+ public static final int PERMISSION_READ = 0x01;
+
+ /**
+ * Descriptor permission: Allow encrypted read operations
+ */
+ public static final int PERMISSION_READ_ENCRYPTED = 0x02;
+
+ /**
+ * Descriptor permission: Allow reading with man-in-the-middle protection
+ */
+ public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
+
+ /**
+ * Descriptor write permission
+ */
+ public static final int PERMISSION_WRITE = 0x10;
+
+ /**
+ * Descriptor permission: Allow encrypted writes
+ */
+ public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
+
+ /**
+ * Descriptor permission: Allow encrypted writes with man-in-the-middle
+ * protection
+ */
+ public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
+
+ /**
+ * Descriptor permission: Allow signed write operations
+ */
+ public static final int PERMISSION_WRITE_SIGNED = 0x80;
+
+ /**
+ * Descriptor permission: Allow signed write operations with
+ * man-in-the-middle protection
+ */
+ public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
+
+ /**
+ * The UUID of this descriptor.
+ * @hide
+ */
+ protected UUID mUuid;
+
+ /**
+ * Permissions for this descriptor
+ * @hide
+ */
+ protected int mPermissions;
+
+ /**
+ * Back-reference to the characteristic this descriptor belongs to.
+ * @hide
+ */
+ protected BluetoothGattCharacteristic mCharacteristic;
+
+ /**
+ * The value for this descriptor.
+ * @hide
+ */
+ protected byte[] mValue;
+
+ /**
+ * Create a new BluetoothGattDescriptor.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic The characteristic this descriptor belongs to
+ * @param uuid The UUID for this descriptor
+ * @param permissions Permissions for this descriptor
+ */
+ /*package*/ BluetoothGattDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid,
+ int permissions) {
+ mCharacteristic = characteristic;
+ mUuid = uuid;
+ mPermissions = permissions;
+ }
+
+ /**
+ * Returns the characteristic this descriptor belongs to.
+ * @return The characteristic.
+ */
+ public BluetoothGattCharacteristic getCharacteristic() {
+ return mCharacteristic;
+ }
+
+ /**
+ * Returns the UUID of this descriptor.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return UUID of this descriptor
+ */
+ public UUID getUuid() {
+ return mUuid;
+ }
+
+ /**
+ * Returns the permissions for this descriptor.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Permissions of this descriptor
+ */
+ public int getPermissions() {
+ return mPermissions;
+ }
+
+ /**
+ * Returns the stored value for this descriptor
+ *
+ * <p>This function returns the stored value for this descriptor as
+ * retrieved by calling {@link BluetoothGatt#readDescriptor}. To cached
+ * value of the descriptor is updated as a result of a descriptor read
+ * operation.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Cached value of the descriptor
+ */
+ public byte[] getValue() {
+ return mValue;
+ }
+
+ /**
+ * Updates the locally stored value of this descriptor.
+ *
+ * <p>This function modifies the locally stored cached value of this
+ * descriptor. To send the value to the remote device, call
+ * {@link BluetoothGatt#writeDescriptor} to send the value to the
+ * remote device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param value New value for this descriptor
+ * @return true if the locally stored value has been set, false if the
+ * requested value could not be stored locally.
+ */
+ public boolean setValue(byte[] value) {
+ mValue = value;
+ return true;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
new file mode 100644
index 0000000..91a1a94
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -0,0 +1,900 @@
+/*
+ * 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.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Public API for the Bluetooth Gatt Profile server role.
+ *
+ * <p>This class provides Bluetooth Gatt server role functionality,
+ * allowing applications to create and advertise Bluetooth Smart services
+ * and characteristics.
+ *
+ * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service
+ * via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the
+ * BluetoothGatt proxy object.
+ * @hide
+ */
+public final class BluetoothGattServer implements BluetoothProfile {
+ private static final String TAG = "BluetoothGattServer";
+ private static final boolean DBG = true;
+
+ private Context mContext;
+ private ServiceListener mServiceListener;
+ private BluetoothAdapter mAdapter;
+ private IBluetoothGatt mService;
+ private BluetoothGattServerCallback mCallback;
+ private int mServerIf;
+
+ private List<BluetoothGattService> mServices;
+
+ /**
+ * Bluetooth state change handlers
+ */
+ private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ if (DBG) 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 (DBG) Log.d(TAG,"Binding service...");
+ if (!mContext.bindService(new
+ Intent(IBluetoothGatt.class.getName()),
+ mConnection, 0)) {
+ Log.e(TAG, "Could not bind to Bluetooth GATT Service");
+ }
+ }
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Service binder handling
+ */
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (DBG) Log.d(TAG, "Proxy object connected");
+ mService = IBluetoothGatt.Stub.asInterface(service);
+ ServiceListener serviceListner = mServiceListener;
+ if (serviceListner != null) {
+ serviceListner.onServiceConnected(BluetoothProfile.GATT_SERVER,
+ BluetoothGattServer.this);
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) Log.d(TAG, "Proxy object disconnected");
+ mService = null;
+ ServiceListener serviceListner = mServiceListener;
+ if (serviceListner != null) {
+ serviceListner.onServiceDisconnected(BluetoothProfile.GATT_SERVER);
+ }
+ }
+ };
+
+ /**
+ * Bluetooth GATT interface callbacks
+ */
+ private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
+ new IBluetoothGattServerCallback.Stub() {
+ /**
+ * Application interface registered - app is ready to go
+ * @hide
+ */
+ public void onServerRegistered(int status, int serverIf) {
+ if (DBG) Log.d(TAG, "onServerRegistered() - status=" + status
+ + " serverIf=" + serverIf);
+ mServerIf = serverIf;
+ try {
+ mCallback.onAppRegistered(status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Callback reporting an LE scan result.
+ * @hide
+ */
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
+
+ try {
+ mCallback.onScanResult(mAdapter.getRemoteDevice(address),
+ rssi, advData);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Server connection state changed
+ * @hide
+ */
+ public void onServerConnectionState(int status, int serverIf,
+ boolean connected, String address) {
+ if (DBG) Log.d(TAG, "onServerConnectionState() - status=" + status
+ + " serverIf=" + serverIf + " device=" + address);
+ try {
+ mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
+ connected ? BluetoothProfile.STATE_CONNECTED :
+ BluetoothProfile.STATE_DISCONNECTED);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Service has been added
+ * @hide
+ */
+ public void onServiceAdded(int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcId) {
+ UUID srvcUuid = srvcId.getUuid();
+ if (DBG) Log.d(TAG, "onServiceAdded() - service=" + srvcUuid
+ + "status=" + status);
+
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ try {
+ mCallback.onServiceAdded((int)status, service);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote client characteristic read request.
+ * @hide
+ */
+ public void onCharacteristicReadRequest(String address, int transId,
+ int offset, boolean isLong, int srvcType, int srvcInstId,
+ ParcelUuid srvcId, int charInstId, ParcelUuid charId) {
+ UUID srvcUuid = srvcId.getUuid();
+ UUID charUuid = charId.getUuid();
+ if (DBG) Log.d(TAG, "onCharacteristicReadRequest() - "
+ + "service=" + srvcUuid + ", characteristic=" + charUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid);
+ if (characteristic == null) return;
+
+ try {
+ mCallback.onCharacteristicReadRequest(device, transId, offset, characteristic);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote client descriptor read request.
+ * @hide
+ */
+ public void onDescriptorReadRequest(String address, int transId,
+ int offset, boolean isLong, int srvcType, int srvcInstId,
+ ParcelUuid srvcId, int charInstId, ParcelUuid charId,
+ ParcelUuid descrId) {
+ UUID srvcUuid = srvcId.getUuid();
+ UUID charUuid = charId.getUuid();
+ UUID descrUuid = descrId.getUuid();
+ if (DBG) Log.d(TAG, "onCharacteristicReadRequest() - "
+ + "service=" + srvcUuid + ", characteristic=" + charUuid
+ + "descriptor=" + descrUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+ if (characteristic == null) return;
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
+ if (descriptor == null) return;
+
+ try {
+ mCallback.onDescriptorReadRequest(device, transId, offset, descriptor);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote client characteristic write request.
+ * @hide
+ */
+ public void onCharacteristicWriteRequest(String address, int transId,
+ int offset, int length, boolean isPrep, boolean needRsp,
+ int srvcType, int srvcInstId, ParcelUuid srvcId,
+ int charInstId, ParcelUuid charId, byte[] value) {
+ UUID srvcUuid = srvcId.getUuid();
+ UUID charUuid = charId.getUuid();
+ if (DBG) Log.d(TAG, "onCharacteristicWriteRequest() - "
+ + "service=" + srvcUuid + ", characteristic=" + charUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+ if (characteristic == null) return;
+
+ try {
+ mCallback.onCharacteristicWriteRequest(device, transId, characteristic,
+ isPrep, needRsp, offset, value);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+
+ }
+
+ /**
+ * Remote client descriptor write request.
+ * @hide
+ */
+ public void onDescriptorWriteRequest(String address, int transId,
+ int offset, int length, boolean isPrep, boolean needRsp,
+ int srvcType, int srvcInstId, ParcelUuid srvcId,
+ int charInstId, ParcelUuid charId, ParcelUuid descrId,
+ byte[] value) {
+ UUID srvcUuid = srvcId.getUuid();
+ UUID charUuid = charId.getUuid();
+ UUID descrUuid = descrId.getUuid();
+ if (DBG) Log.d(TAG, "onDescriptorWriteRequest() - "
+ + "service=" + srvcUuid + ", characteristic=" + charUuid
+ + "descriptor=" + descrUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+ if (characteristic == null) return;
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
+ if (descriptor == null) return;
+
+ try {
+ mCallback.onDescriptorWriteRequest(device, transId, descriptor,
+ isPrep, needRsp, offset, value);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Execute pending writes.
+ * @hide
+ */
+ public void onExecuteWrite(String address, int transId,
+ boolean execWrite) {
+ if (DBG) Log.d(TAG, "onExecuteWrite() - "
+ + "device=" + address + ", transId=" + transId
+ + "execWrite=" + execWrite);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ if (device == null) return;
+
+ try {
+ mCallback.onExecuteWrite(device, transId, execWrite);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+ };
+
+ /**
+ * Create a BluetoothGattServer proxy object.
+ */
+ /*package*/ BluetoothGattServer(Context context, ServiceListener l) {
+ mContext = context;
+ mServiceListener = l;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mServices = new ArrayList<BluetoothGattService>();
+
+ IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+ if (b != null) {
+ IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to register BluetoothStateChangeCallback", re);
+ }
+ } else {
+ Log.e(TAG, "Unable to get BluetoothManager interface.");
+ throw new RuntimeException("BluetoothManager inactive");
+ }
+
+ //Bind to the service only if the Bluetooth is ON
+ if(mAdapter.isEnabled()){
+ if (!context.bindService(new Intent(IBluetoothGatt.class.getName()), mConnection, 0)) {
+ Log.e(TAG, "Could not bind to Bluetooth Gatt Service");
+ }
+ }
+ }
+
+ /**
+ * Close the connection to the gatt service.
+ */
+ /*package*/ void close() {
+ if (DBG) Log.d(TAG, "close()");
+
+ unregisterApp();
+ mServiceListener = null;
+
+ IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+ if (b != null) {
+ IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+ try {
+ mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Unable to unregister BluetoothStateChangeCallback", re);
+ }
+ }
+
+ synchronized (mConnection) {
+ if (mService != null) {
+ try {
+ mService = null;
+ mContext.unbindService(mConnection);
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a service by UUID, instance and type.
+ * @hide
+ */
+ /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) {
+ for(BluetoothGattService svc : mServices) {
+ if (svc.getType() == type &&
+ svc.getInstanceId() == instanceId &&
+ svc.getUuid().equals(uuid)) {
+ return svc;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Register an application callback to start using Gatt.
+ *
+ * <p>This is an asynchronous call. The callback is used to notify
+ * success or failure if the function returns true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param callback Gatt callback handler that will receive asynchronous
+ * callbacks.
+ * @return true, if application was successfully registered.
+ */
+ public boolean registerApp(BluetoothGattServerCallback callback) {
+ if (DBG) Log.d(TAG, "registerApp()");
+ if (mService == null) return false;
+
+ mCallback = callback;
+ UUID uuid = UUID.randomUUID();
+ if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
+
+ try {
+ mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Unregister the current application and callbacks.
+ */
+ public void unregisterApp() {
+ if (DBG) Log.d(TAG, "unregisterApp() - mServerIf=" + mServerIf);
+ if (mService == null || mServerIf == 0) return;
+
+ try {
+ mCallback = null;
+ mService.unregisterServer(mServerIf);
+ mServerIf = 0;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Starts a scan for Bluetooth LE devices.
+ *
+ * <p>Results of the scan are reported using the
+ * {@link BluetoothGattServerCallback#onScanResult} callback.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return true, if the scan was started successfully
+ */
+ public boolean startScan() {
+ if (DBG) Log.d(TAG, "startScan()");
+ if (mService == null || mServerIf == 0) return false;
+
+ try {
+ mService.startScan(mServerIf, true);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Starts a scan for Bluetooth LE devices, looking for devices that
+ * advertise given services.
+ *
+ * <p>Devices which advertise all specified services are reported using the
+ * {@link BluetoothGattServerCallback#onScanResult} callback.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param serviceUuids Array of services to look for
+ * @return true, if the scan was started successfully
+ */
+ public boolean startScan(UUID[] serviceUuids) {
+ if (DBG) Log.d(TAG, "startScan() - with UUIDs");
+ if (mService == null || mServerIf == 0) return false;
+
+ try {
+ ParcelUuid[] uuids = new ParcelUuid[serviceUuids.length];
+ for(int i = 0; i != uuids.length; ++i) {
+ uuids[i] = new ParcelUuid(serviceUuids[i]);
+ }
+ mService.startScanWithUuids(mServerIf, true, uuids);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Stops an ongoing Bluetooth LE device scan.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ */
+ public void stopScan() {
+ if (DBG) Log.d(TAG, "stopScan()");
+ if (mService == null || mServerIf == 0) return;
+
+ try {
+ mService.stopScan(mServerIf, true);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Initiate a connection to a Bluetooth Gatt capable device.
+ *
+ * <p>The connection may not be established right away, but will be
+ * completed when the remote device is available. A
+ * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+ * invoked when the connection state changes as a result of this function.
+ *
+ * <p>The autoConnect paramter determines whether to actively connect to
+ * the remote device, or rather passively scan and finalize the connection
+ * when the remote device is in range/available. Generally, the first ever
+ * connection to a device should be direct (autoConnect set to false) and
+ * subsequent connections to known devices should be invoked with the
+ * autoConnect parameter set to false.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device to connect to
+ * @param autoConnect Whether to directly connect to the remote device (false)
+ * or to automatically connect as soon as the remote
+ * device becomes available (true).
+ * @return true, if the connection attempt was initiated successfully
+ */
+ public boolean connect(BluetoothDevice device, boolean autoConnect) {
+ if (DBG) Log.d(TAG, "connect: " + device.getAddress() + ", auto: " + autoConnect);
+ if (mService == null || mServerIf == 0) return false;
+
+ try {
+ mService.serverConnect(mServerIf, device.getAddress(),
+ autoConnect ? false : true); // autoConnect is inverse of "isDirect"
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Disconnects an established connection, or cancels a connection attempt
+ * currently in progress.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device
+ */
+ public void cancelConnection(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
+ if (mService == null || mServerIf == 0) return;
+
+ try {
+ mService.serverDisconnect(mServerIf, device.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Send a response to a read or write request to a remote device.
+ *
+ * <p>This function must be invoked in when a remote read/write request
+ * is received by one of these callback methots:
+ *
+ * <ul>
+ * <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
+ * <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
+ * <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
+ * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
+ * </ul>
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device The remote device to send this response to
+ * @param requestId The ID of the request that was received with the callback
+ * @param status The status of the request to be sent to the remote devices
+ * @param offset Value offset for partial read/write response
+ * @param value The value of the attribute that was read/written (optional)
+ */
+ public boolean sendResponse(BluetoothDevice device, int requestId,
+ int status, int offset, byte[] value) {
+ if (DBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
+ if (mService == null || mServerIf == 0) return false;
+
+ try {
+ mService.sendResponse(mServerIf, device.getAddress(), requestId,
+ status, offset, value);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Send a notification or indication that a local characteristic has been
+ * updated.
+ *
+ * <p>A notification or indication is sent to the remote device to signal
+ * that the characteristic has been updated. This function should be invoked
+ * for every client that requests notifications/indications by writing
+ * to the "Client Configuration" descriptor for the given characteristic.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device The remote device to receive the notification/indication
+ * @param characteristic The local characteristic that has been updated
+ * @param confirm true to request confirmation from the client (indication),
+ * false to send a notification
+ * @return true, if the notification has been triggered successfully
+ */
+ public boolean notifyCharacteristicChanged(BluetoothDevice device,
+ BluetoothGattCharacteristic characteristic, boolean confirm) {
+ if (DBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
+ if (mService == null || mServerIf == 0) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ try {
+ mService.sendNotification(mServerIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()), confirm,
+ characteristic.getValue());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a service to the list of services to be advertised.
+ *
+ * <p>Once a service has been addded to the the list, the service and it's
+ * included characteristics will be advertised by the local device.
+ *
+ * <p>If the local device is already advertising services when this function
+ * is called, a service update notification will be sent to all clients.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param service Service to be added to the list of services advertised
+ * by this device.
+ * @return true, if the service has been added successfully
+ */
+ public boolean addService(BluetoothGattService service) {
+ if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
+ if (mService == null || mServerIf == 0) return false;
+
+ mServices.add(service);
+
+ try {
+ mService.beginServiceDeclaration(mServerIf, service.getType(),
+ service.getInstanceId(), service.getHandles(),
+ new ParcelUuid(service.getUuid()));
+
+ List<BluetoothGattService> includedServices = service.getIncludedServices();
+ for (BluetoothGattService includedService : includedServices) {
+ mService.addIncludedService(mServerIf,
+ includedService.getType(),
+ includedService.getInstanceId(),
+ new ParcelUuid(includedService.getUuid()));
+ }
+
+ List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
+ int permission = ((characteristic.getKeySize() - 7) << 12)
+ + characteristic.getPermissions();
+ mService.addCharacteristic(mServerIf,
+ new ParcelUuid(characteristic.getUuid()),
+ characteristic.getProperties(), permission);
+
+ List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
+ for (BluetoothGattDescriptor descriptor: descriptors) {
+ mService.addDescriptor(mServerIf,
+ new ParcelUuid(descriptor.getUuid()),
+ descriptor.getPermissions());
+ }
+ }
+
+ mService.endServiceDeclaration(mServerIf);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Removes a service from the list of services to be advertised.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param service Service to beremoved.
+ * @return true, if the service has been removed
+ */
+ public boolean removeService(BluetoothGattService service) {
+ if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
+ if (mService == null || mServerIf == 0) return false;
+
+ BluetoothGattService intService = getService(service.getUuid(),
+ service.getInstanceId(), service.getType());
+ if (intService == null) return false;
+
+ try {
+ mService.removeService(mServerIf, service.getType(),
+ service.getInstanceId(), new ParcelUuid(service.getUuid()));
+ mServices.remove(intService);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove all services from the list of advertised services.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ */
+ public void clearServices() {
+ if (DBG) Log.d(TAG, "clearServices()");
+ if (mService == null || mServerIf == 0) return;
+
+ try {
+ mService.clearServices(mServerIf);
+ mServices.clear();
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Returns a list of GATT services offered bu this device.
+ *
+ * <p>An application must call {@link #addService} to add a serice to the
+ * list of services offered by this device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return List of services. Returns an empty list
+ * if no services have been added yet.
+ */
+ public List<BluetoothGattService> getServices() {
+ return mServices;
+ }
+
+ /**
+ * Returns a {@link BluetoothGattService} from the list of services offered
+ * by this device.
+ *
+ * <p>If multiple instances of the same service (as identified by UUID)
+ * exist, the first instance of the service is returned.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param uuid UUID of the requested service
+ * @return BluetoothGattService if supported, or null if the requested
+ * service is not offered by this device.
+ */
+ public BluetoothGattService getService(UUID uuid) {
+ for (BluetoothGattService service : mServices) {
+ if (service.getUuid().equals(uuid)) {
+ return service;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the current connection state of the profile.
+ *
+ * <p>This is not specific to any application configuration but represents
+ * the connection state of the local Bluetooth adapter for this profile.
+ * This can be used by applications like status bar which would just like
+ * to know the state of the local adapter.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote bluetooth device.
+ * @return State of the profile connection. One of
+ * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
+ */
+ @Override
+ public int getConnectionState(BluetoothDevice device) {
+ if (DBG) Log.d(TAG,"getConnectionState()");
+ if (mService == null) return STATE_DISCONNECTED;
+
+ List<BluetoothDevice> connectedDevices = getConnectedDevices();
+ for(BluetoothDevice connectedDevice : connectedDevices) {
+ if (device.equals(connectedDevice)) {
+ return STATE_CONNECTED;
+ }
+ }
+
+ return STATE_DISCONNECTED;
+ }
+
+ /**
+ * Get connected devices for the Gatt profile.
+ *
+ * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+ *
+ * <p>This is not specific to any application configuration but represents
+ * the connection state of the local Bluetooth adapter for this profile.
+ * This can be used by applications like status bar which would just like
+ * to know the state of the local adapter.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return List of devices. The list will be empty on error.
+ */
+ @Override
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (DBG) Log.d(TAG,"getConnectedDevices");
+
+ List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>();
+ if (mService == null) return connectedDevices;
+
+ try {
+ connectedDevices = mService.getDevicesMatchingConnectionStates(
+ new int[] { BluetoothProfile.STATE_CONNECTED });
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+
+ return connectedDevices;
+ }
+
+ /**
+ * Get a list of devices that match any of the given connection
+ * states.
+ *
+ * <p> If none of the devices match any of the given states,
+ * an empty list will be returned.
+ *
+ * <p>This is not specific to any application configuration but represents
+ * the connection state of the local Bluetooth adapter for this profile.
+ * This can be used by applications like status bar which would just like
+ * to know the state of the local adapter.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param states Array of states. States can be one of
+ * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
+ * @return List of devices. The list will be empty on error.
+ */
+ @Override
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (DBG) Log.d(TAG,"getDevicesMatchingConnectionStates");
+
+ List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ if (mService == null) return devices;
+
+ try {
+ devices = mService.getDevicesMatchingConnectionStates(states);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+
+ return devices;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
new file mode 100644
index 0000000..4f608ff
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+import android.util.Log;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGattServer} callbacks.
+ * @hide
+ */
+public abstract class BluetoothGattServerCallback {
+ /**
+ * Callback to inform change in registration state of the application.
+ *
+ * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the application
+ * was successfully registered.
+ */
+ public void onAppRegistered(int status) {
+ }
+
+ /**
+ * Callback reporting an LE device found during a device scan initiated
+ * by the {@link BluetoothGattServer#startScan} function.
+ *
+ * @param device Identifies the remote device
+ * @param rssi The RSSI value for the remote device as reported by the
+ * Bluetooth hardware. 0 if no RSSI value is available.
+ * @param scanRecord The content of the advertisement record offered by
+ * the remote device.
+ */
+ public void onScanResult(BluetoothDevice device, int rssi, byte[] scanRecord) {
+ }
+
+ /**
+ * Callback indicating when a remote device has been connected or disconnected.
+ *
+ * @param device Remote device that has been connected or disconnected.
+ * @param status Status of the connect or disconnect operation.
+ * @param newState Returns the new connection state. Can be one of
+ * {@link BluetoothProfile#STATE_DISCONNECTED} or
+ * {@link BluetoothProfile#STATE_CONNECTED}
+ */
+ public void onConnectionStateChange(BluetoothDevice device, int status,
+ int newState) {
+ }
+
+ /**
+ * Indicates whether a local service has been added successfully.
+ *
+ * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the service
+ * was added successfully.
+ * @param service The service that has been added
+ */
+ public void onServiceAdded(int status, BluetoothGattService service) {
+ }
+
+ /**
+ * A remote client has requested to read a local characteristic.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the read operation
+ * @param requestId The Id of the request
+ * @param offset Offset into the value of the characteristic
+ * @param characteristic Characteristic to be read
+ */
+ public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
+ int offset, BluetoothGattCharacteristic characteristic) {
+ }
+
+ /**
+ * A remote client has requested to write to a local characteristic.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the write operation
+ * @param requestId The Id of the request
+ * @param characteristic Characteristic to be written to.
+ * @param preparedWrite true, if this write operation should be queued for
+ * later execution.
+ * @param responseNeeded true, if the remote device requires a response
+ * @param offset The offset given for the value
+ * @param value The value the client wants to assign to the characteristic
+ */
+ public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
+ BluetoothGattCharacteristic characteristic,
+ boolean preparedWrite, boolean responseNeeded,
+ int offset, byte[] value) {
+ }
+
+ /**
+ * A remote client has requested to read a local descriptor.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the read operation
+ * @param requestId The Id of the request
+ * @param offset Offset into the value of the characteristic
+ * @param descriptor Descriptor to be read
+ */
+ public void onDescriptorReadRequest(BluetoothDevice device, int requestId,
+ int offset, BluetoothGattDescriptor descriptor) {
+ }
+
+ /**
+ * A remote client has requested to write to a local descriptor.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the write operation
+ * @param requestId The Id of the request
+ * @param descriptor Descriptor to be written to.
+ * @param preparedWrite true, if this write operation should be queued for
+ * later execution.
+ * @param responseNeeded true, if the remote device requires a response
+ * @param offset The offset given for the value
+ * @param value The value the client wants to assign to the descriptor
+ */
+ public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
+ BluetoothGattDescriptor descriptor,
+ boolean preparedWrite, boolean responseNeeded,
+ int offset, byte[] value) {
+ }
+
+ /**
+ * Execute all pending write operations for this device.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the write operations
+ * @param requestId The Id of the request
+ * @param execute Whether the pending writes should be executed (true) or
+ * cancelled (false)
+ */
+ public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
new file mode 100644
index 0000000..6a3ce66e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -0,0 +1,232 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth Gatt Service
+ * @hide
+ */
+public class BluetoothGattService {
+
+ /**
+ * Primary service
+ */
+ public static final int SERVICE_TYPE_PRIMARY = 0;
+
+ /**
+ * Secondary service (included by primary services)
+ */
+ public static final int SERVICE_TYPE_SECONDARY = 1;
+
+
+ /**
+ * The remote device his service is associated with.
+ * This applies to client applications only.
+ * @hide
+ */
+ protected BluetoothDevice mDevice;
+
+ /**
+ * The UUID of this service.
+ * @hide
+ */
+ protected UUID mUuid;
+
+ /**
+ * Instance ID for this service.
+ * @hide
+ */
+ protected int mInstanceId;
+
+ /**
+ * Handle counter override (for conformance testing).
+ * @hide
+ */
+ protected int mHandles = 0;
+
+ /**
+ * Service type (Primary/Secondary).
+ * @hide
+ */
+ protected int mServiceType;
+
+ /**
+ * List of characteristics included in this service.
+ */
+ protected List<BluetoothGattCharacteristic> mCharacteristics;
+
+ /**
+ * List of included services for this service.
+ */
+ protected List<BluetoothGattService> mIncludedServices;
+
+ /**
+ * Create a new BluetoothGattService.
+ * @hide
+ */
+ /*package*/ BluetoothGattService(UUID uuid, int serviceType) {
+ mDevice = null;
+ mUuid = uuid;
+ mInstanceId = 0;
+ mServiceType = serviceType;
+ mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+ mIncludedServices = new ArrayList<BluetoothGattService>();
+ }
+
+ /**
+ * Create a new BluetoothGattService
+ * @hide
+ */
+ /*package*/ BluetoothGattService(BluetoothDevice device, UUID uuid,
+ int instanceId, int serviceType) {
+ mDevice = device;
+ mUuid = uuid;
+ mInstanceId = instanceId;
+ mServiceType = serviceType;
+ mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+ mIncludedServices = new ArrayList<BluetoothGattService>();
+ }
+
+ /**
+ * Returns the device associated with this service.
+ * @hide
+ */
+ /*package*/ BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * Add a characteristic to this service.
+ * @hide
+ */
+ /*package*/ void addCharacteristic(BluetoothGattCharacteristic characteristic) {
+ mCharacteristics.add(characteristic);
+ }
+
+ /**
+ * Get characteristic by UUID and instanceId.
+ * @hide
+ */
+ /*package*/ BluetoothGattCharacteristic getCharacteristic(UUID uuid, int instanceId) {
+ for(BluetoothGattCharacteristic characteristic : mCharacteristics) {
+ if (uuid.equals(characteristic.getUuid()) &&
+ mInstanceId == instanceId)
+ return characteristic;
+ }
+ return null;
+ }
+
+ /**
+ * Get the handle count override (conformance testing.
+ * @hide
+ */
+ /*package*/ int getHandles() {
+ return mHandles;
+ }
+
+ /**
+ * Add an included service to the internal map.
+ * @hide
+ */
+ /*package*/ void addIncludedService(BluetoothGattService includedService) {
+ mIncludedServices.add(includedService);
+ }
+
+ /**
+ * Returns the UUID of this service
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return UUID of this service
+ */
+ public UUID getUuid() {
+ return mUuid;
+ }
+
+ /**
+ * Returns the instance ID for this service
+ *
+ * <p>If a remote device offers multiple services with the same UUID
+ * (ex. multiple battery services for different batteries), the instance
+ * ID is used to distuinguish services.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Instance ID of this service
+ */
+ public int getInstanceId() {
+ return mInstanceId;
+ }
+
+ /**
+ * Get the type of this service (primary/secondary)
+ * @hide
+ */
+ public int getType() {
+ return mServiceType;
+ }
+
+ /**
+ * Get the list of included Gatt services for this service.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return List of included services or empty list if no included services
+ * were discovered.
+ */
+ public List<BluetoothGattService> getIncludedServices() {
+ return mIncludedServices;
+ }
+
+ /**
+ * Returns a list of characteristics included in this service.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Characteristics included in this service
+ */
+ public List<BluetoothGattCharacteristic> getCharacteristics() {
+ return mCharacteristics;
+ }
+
+ /**
+ * Returns a characteristic with a given UUID out of the list of
+ * characteristics offered by this service.
+ *
+ * <p>This is a convenience function to allow access to a given characteristic
+ * without enumerating over the list returned by {@link #getCharacteristics}
+ * manually.
+ *
+ * <p>If a remote service offers multiple characteristics with the same
+ * UUID, the first instance of a characteristic with the given UUID
+ * is returned.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return Gatt characteristic object or null if no characteristic with the
+ * given UUID was found.
+ */
+ public BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+ for(BluetoothGattCharacteristic characteristic : mCharacteristics) {
+ if (uuid.equals(characteristic.getUuid()))
+ return characteristic;
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
old mode 100755
new mode 100644
index 1920efa..9ee202a
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -88,6 +88,18 @@
public static final int PBAP = 6;
/**
+ * GATT
+ * @hide
+ */
+ static public final int GATT = 7;
+
+ /**
+ * GATT_SERVER
+ * @hide
+ */
+ static public final int GATT_SERVER = 8;
+
+ /**
* Default priority for devices that we try to auto-connect to and
* and allow incoming connections for the profile
* @hide
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
new file mode 100644
index 0000000..c89d132
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -0,0 +1,90 @@
+/*
+ * 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;
+import android.os.ParcelUuid;
+
+import android.bluetooth.IBluetoothGattCallback;
+import android.bluetooth.IBluetoothGattServerCallback;
+
+/**
+ * API for interacting with BLE / GATT
+ * @hide
+ */
+interface IBluetoothGatt {
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+
+ void startScan(in int appIf, in boolean isServer);
+ void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids);
+ void stopScan(in int appIf, in boolean isServer);
+
+ void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
+ void unregisterClient(in int clientIf);
+ void clientConnect(in int clientIf, in String address, in boolean isDirect);
+ void clientDisconnect(in int clientIf, in String address);
+ void refreshDevice(in int clientIf, in String address);
+ void discoverServices(in int clientIf, in String address);
+ void readCharacteristic(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in int authReq);
+ void writeCharacteristic(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in int writeType, in int authReq, in byte[] value);
+ void readDescriptor(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in ParcelUuid descrUuid, in int authReq);
+ void writeDescriptor(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in ParcelUuid descrId, in int writeType,
+ in int authReq, in byte[] value);
+ void registerForNotification(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in boolean enable);
+ void beginReliableWrite(in int clientIf, in String address);
+ void endReliableWrite(in int clientIf, in String address, in boolean execute);
+ void readRemoteRssi(in int clientIf, in String address);
+
+ void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallback callback);
+ void unregisterServer(in int serverIf);
+ void serverConnect(in int servertIf, in String address, in boolean isDirect);
+ void serverDisconnect(in int serverIf, in String address);
+ void beginServiceDeclaration(in int serverIf, in int srvcType,
+ in int srvcInstanceId, in int minHandles,
+ in ParcelUuid srvcId);
+ void addIncludedService(in int serverIf, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId);
+ void addCharacteristic(in int serverIf, in ParcelUuid charId,
+ in int properties, in int permissions);
+ void addDescriptor(in int serverIf, in ParcelUuid descId,
+ in int permissions);
+ void endServiceDeclaration(in int serverIf);
+ void removeService(in int serverIf, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId);
+ void clearServices(in int serverIf);
+ void sendResponse(in int serverIf, in String address, in int requestId,
+ in int status, in int offset, in byte[] value);
+ void sendNotification(in int serverIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in boolean confirm, in byte[] value);
+}
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
new file mode 100644
index 0000000..fc52172
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -0,0 +1,65 @@
+/*
+ * 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.os.ParcelUuid;
+
+
+/**
+ * Callback definitions for interacting with BLE / GATT
+ * @hide
+ */
+interface IBluetoothGattCallback {
+ void onClientRegistered(in int status, in int clientIf);
+ void onClientConnectionState(in int status, in int clientIf,
+ in boolean connected, in String address);
+ void onScanResult(in String address, in int rssi, in byte[] advData);
+ void onGetService(in String address, in int srvcType, in int srvcInstId,
+ in ParcelUuid srvcUuid);
+ void onGetIncludedService(in String address, in int srvcType, in int srvcInstId,
+ in ParcelUuid srvcUuid, in int inclSrvcType,
+ in int inclSrvcInstId, in ParcelUuid inclSrvcUuid);
+ void onGetCharacteristic(in String address, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in int charProps);
+ void onGetDescriptor(in String address, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in ParcelUuid descrUuid);
+ void onSearchComplete(in String address, in int status);
+ void onCharacteristicRead(in String address, in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in byte[] value);
+ void onCharacteristicWrite(in String address, in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid);
+ void onExecuteWrite(in String address, in int status);
+ void onDescriptorRead(in String address, in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in ParcelUuid descrUuid, in byte[] value);
+ void onDescriptorWrite(in String address, in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in ParcelUuid descrUuid);
+ void onNotify(in String address, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in byte[] value);
+ void onReadRemoteRssi(in String address, in int rssi, in int status);
+}
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
new file mode 100644
index 0000000..ae9bffc
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
@@ -0,0 +1,61 @@
+/*
+ * 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.os.ParcelUuid;
+
+
+/**
+ * Callback definitions for interacting with BLE / GATT
+ * @hide
+ */
+interface IBluetoothGattServerCallback {
+ void onServerRegistered(in int status, in int serverIf);
+ void onScanResult(in String address, in int rssi, in byte[] advData);
+ void onServerConnectionState(in int status, in int serverIf,
+ in boolean connected, in String address);
+ void onServiceAdded(in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId);
+ void onCharacteristicReadRequest(in String address, in int transId,
+ in int offset, in boolean isLong,
+ in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId,
+ in int charInstId, in ParcelUuid charId);
+ void onDescriptorReadRequest(in String address, in int transId,
+ in int offset, in boolean isLong,
+ in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId,
+ in int charInstId, in ParcelUuid charId,
+ in ParcelUuid descrId);
+ void onCharacteristicWriteRequest(in String address, in int transId,
+ in int offset, in int length,
+ in boolean isPrep,
+ in boolean needRsp,
+ in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId,
+ in int charInstId, in ParcelUuid charId,
+ in byte[] value);
+ void onDescriptorWriteRequest(in String address, in int transId,
+ in int offset, in int length,
+ in boolean isPrep,
+ in boolean needRsp,
+ in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId,
+ in int charInstId, in ParcelUuid charId,
+ in ParcelUuid descrId,
+ in byte[] value);
+ void onExecuteWrite(in String address, in int transId, in boolean execWrite);
+}
diff --git a/core/java/android/bluetooth/MutableBluetoothGattCharacteristic.java b/core/java/android/bluetooth/MutableBluetoothGattCharacteristic.java
new file mode 100644
index 0000000..c05abb2
--- /dev/null
+++ b/core/java/android/bluetooth/MutableBluetoothGattCharacteristic.java
@@ -0,0 +1,67 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.IllegalFormatConversionException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Mutable variant of a Bluetooth Gatt Characteristic
+ * @hide
+ */
+public class MutableBluetoothGattCharacteristic extends BluetoothGattCharacteristic {
+
+ /**
+ * Create a new MutableBluetoothGattCharacteristic.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param uuid The UUID for this characteristic
+ * @param properties Properties of this characteristic
+ * @param permissions Permissions for this characteristic
+ */
+ public MutableBluetoothGattCharacteristic(UUID uuid, int properties, int permissions) {
+ super(null, uuid, 0, properties, permissions);
+ }
+
+ /**
+ * Adds a descriptor to this characteristic.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param descriptor Descriptor to be added to this characteristic.
+ */
+ public void addDescriptor(MutableBluetoothGattDescriptor descriptor) {
+ mDescriptors.add(descriptor);
+ descriptor.setCharacteristic(this);
+ }
+
+ /**
+ * Set the desired key size.
+ * @hide
+ */
+ public void setKeySize(int keySize) {
+ mKeySize = keySize;
+ }
+
+ /**
+ * Sets the service associated with this device.
+ * @hide
+ */
+ /*package*/ void setService(BluetoothGattService service) {
+ mService = service;
+ }
+}
diff --git a/core/java/android/bluetooth/MutableBluetoothGattDescriptor.java b/core/java/android/bluetooth/MutableBluetoothGattDescriptor.java
new file mode 100644
index 0000000..e455392
--- /dev/null
+++ b/core/java/android/bluetooth/MutableBluetoothGattDescriptor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import java.util.UUID;
+
+/**
+ * Mutable variant of a Bluetooth Gatt Descriptor
+ * @hide
+ */
+public class MutableBluetoothGattDescriptor extends BluetoothGattDescriptor {
+
+ /**
+ * Create a new BluetoothGattDescriptor.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param uuid The UUID for this descriptor
+ * @param permissions Permissions for this descriptor
+ */
+ public MutableBluetoothGattDescriptor(UUID uuid, int permissions) {
+ super(null, uuid, permissions);
+ }
+
+ /**
+ * Set the back-reference to the associated characteristic
+ * @hide
+ */
+ /*package*/ void setCharacteristic(BluetoothGattCharacteristic characteristic) {
+ mCharacteristic = characteristic;
+ }
+}
diff --git a/core/java/android/bluetooth/MutableBluetoothGattService.java b/core/java/android/bluetooth/MutableBluetoothGattService.java
new file mode 100644
index 0000000..927f5ab
--- /dev/null
+++ b/core/java/android/bluetooth/MutableBluetoothGattService.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth Gatt Service
+ * @hide
+ */
+public class MutableBluetoothGattService extends BluetoothGattService {
+
+ /**
+ * Create a new MutableBluetoothGattService.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param uuid The UUID for this service
+ * @param serviceType The type of this service (primary/secondary)
+ */
+ public MutableBluetoothGattService(UUID uuid, int serviceType) {
+ super(uuid, serviceType);
+ }
+
+ /**
+ * Add an included service to this service.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param service The service to be added
+ * @return true, if the included service was added to the service
+ */
+ public boolean addService(BluetoothGattService service) {
+ mIncludedServices.add(service);
+ return true;
+ }
+
+ /**
+ * Add a characteristic to this service.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic The characteristics to be added
+ * @return true, if the characteristic was added to the service
+ */
+ public boolean addCharacteristic(MutableBluetoothGattCharacteristic characteristic) {
+ mCharacteristics.add(characteristic);
+ characteristic.setService(this);
+ return true;
+ }
+
+ /**
+ * Force the instance ID.
+ * This is needed for conformance testing only.
+ * @hide
+ */
+ public void setInstanceId(int instanceId) {
+ mInstanceId = instanceId;
+ }
+
+ /**
+ * Force the number of handles to reserve for this service.
+ * This is needed for conformance testing only.
+ * @hide
+ */
+ public void setHandles(int handles) {
+ mHandles = handles;
+ }
+}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 612f1af..8aef405 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -298,7 +298,7 @@
private void enforceFilePermission(String callingPkg, Uri uri, String mode)
throws FileNotFoundException, SecurityException {
- if (mode != null && mode.startsWith("rw")) {
+ if (mode != null && mode.indexOf('w') != -1) {
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
throw new FileNotFoundException("App op not allowed");
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f8ff8d1..60e9f58 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1158,16 +1158,29 @@
public static final String ACTION_ASSIST = "android.intent.action.ASSIST";
/**
- * An optional field on {@link #ACTION_ASSIST} containing the name of the current
- * foreground application package at the time the assist was invoked.
+ * Activity Action: Perform voice assist action.
+ * <p>
+ * Input: {@link #EXTRA_ASSIST_PACKAGE} and {@link #EXTRA_ASSIST_CONTEXT} can provide
+ * additional optional contextual information about where the user was when they requested
+ * the voice assist.
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+ * containing the name of the current foreground application package at the time
+ * the assist was invoked.
*/
public static final String EXTRA_ASSIST_PACKAGE
= "android.intent.extra.ASSIST_PACKAGE";
/**
- * An optional field on {@link #ACTION_ASSIST} containing additional contextual
- * information supplied by the current foreground app at the time of the assist
- * request. This is a {@link Bundle} of additional data.
+ * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+ * containing additional contextual information supplied by the current
+ * foreground app at the time of the assist request. This is a {@link Bundle} of
+ * additional data.
*/
public static final String EXTRA_ASSIST_CONTEXT
= "android.intent.extra.ASSIST_CONTEXT";
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index c765457..4b83611 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -362,4 +362,19 @@
* Clear a process (pid) from being associated with an interface.
*/
void clearDnsInterfaceForPid(int pid);
+
+ /**
+ * Start the clatd (464xlat) service
+ */
+ void startClatd(String interfaceName);
+
+ /**
+ * Stop the clatd (464xlat) service
+ */
+ void stopClatd();
+
+ /**
+ * Determine whether the clatd (464xlat) service has been started
+ */
+ boolean isClatdStarted();
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 03a9b09..9955bc1 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -74,17 +74,13 @@
int mNativeObject; // package scope only for SurfaceControl access
private int mGenerationId; // incremented each time mNativeSurface changes
+ @SuppressWarnings("UnusedDeclaration")
private final Canvas mCanvas = new CompatibleCanvas();
- // The Translator for density compatibility mode. This is used for scaling
- // the canvas to perform the appropriate density transformation.
- private Translator mCompatibilityTranslator;
-
// A matrix to scale the matrix set by application. This is set to null for
// non compatibility mode.
private Matrix mCompatibleMatrix;
-
/**
* Rotation constant: 0 degree rotation (natural orientation)
*/
@@ -105,8 +101,6 @@
*/
public static final int ROTATION_270 = 3;
-
-
/**
* Create an empty surface, which will later be filled in by readFromParcel().
* @hide
@@ -169,6 +163,7 @@
if (mNativeObject != 0) {
nativeRelease(mNativeObject);
mNativeObject = 0;
+ mGenerationId++;
}
mCloseGuard.close();
}
@@ -183,6 +178,7 @@
if (mNativeObject != 0) {
nativeDestroy(mNativeObject);
mNativeObject = 0;
+ mGenerationId++;
}
mCloseGuard.close();
}
@@ -291,6 +287,7 @@
"SurfaceControl native object is null. Are you using a released SurfaceControl?");
}
mNativeObject = nativeCopyFrom(mNativeObject, other.mNativeObject);
+ mGenerationId++;
}
/**
@@ -312,7 +309,10 @@
}
// transfer the reference from other to us
mNativeObject = other.mNativeObject;
+ mGenerationId++;
+
other.mNativeObject = 0;
+ other.mGenerationId++;
}
}
@@ -327,6 +327,7 @@
}
mName = source.readString();
mNativeObject = nativeReadFromParcel(mNativeObject, source);
+ mGenerationId++;
}
@Override
@@ -405,24 +406,6 @@
private Matrix mOrigMatrix = null;
@Override
- public int getWidth() {
- int w = super.getWidth();
- if (mCompatibilityTranslator != null) {
- w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f);
- }
- return w;
- }
-
- @Override
- public int getHeight() {
- int h = super.getHeight();
- if (mCompatibilityTranslator != null) {
- h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f);
- }
- return h;
- }
-
- @Override
public void setMatrix(Matrix matrix) {
if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
// don't scale the matrix if it's not compatibility mode, or
@@ -435,6 +418,7 @@
}
}
+ @SuppressWarnings("deprecation")
@Override
public void getMatrix(Matrix m) {
super.getMatrix(m);
diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java
index 99f20ff..e574593 100644
--- a/core/java/android/webkit/WebIconDatabase.java
+++ b/core/java/android/webkit/WebIconDatabase.java
@@ -25,11 +25,19 @@
* and WebView.getIconDatabase() will return a WebIconDatabase object. This
* WebIconDatabase object is a single instance and all methods operate on that
* single object.
+ * The main use-case for this class is calling {@link #open}
+ * to enable favicon functionality on all WebView instances in this process.
+ *
+ * @deprecated This class is only required when running on devices
+ * up to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
*/
+@Deprecated
public class WebIconDatabase {
/**
* Interface for receiving icons from the database.
+ * @deprecated This interface is obsolete.
*/
+ @Deprecated
public interface IconListener {
/**
* Called when the icon has been retrieved from the database and the
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 92b028e..d901d0a 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -422,7 +422,9 @@
/**
* Sets whether the WebView should save passwords. The default is true.
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
*/
+ @Deprecated
public void setSavePassword(boolean save) {
throw new MustOverrideException();
}
@@ -432,7 +434,9 @@
*
* @return whether the WebView saves passwords
* @see #setSavePassword
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
*/
+ @Deprecated
public boolean getSavePassword() {
throw new MustOverrideException();
}
@@ -515,18 +519,20 @@
/**
* Enables using light touches to make a selection and activate mouseovers.
- * The default is false.
+ * @deprecated From {@link android.os.Build.VERSION_CODES#JELLY_BEAN} this
+ * setting is obsolete and has no effect.
*/
+ @Deprecated
public void setLightTouchEnabled(boolean enabled) {
throw new MustOverrideException();
}
/**
* Gets whether light touches are enabled.
- *
- * @return whether light touches are enabled
* @see #setLightTouchEnabled
+ * @deprecated This setting is obsolete.
*/
+ @Deprecated
public boolean getLightTouchEnabled() {
throw new MustOverrideException();
}
@@ -1012,7 +1018,9 @@
* {@link PluginState#OFF}.
*
* @param state a PluginState value
+ * @deprecated Plugins will not be supported in future, and should not be used.
*/
+ @Deprecated
public synchronized void setPluginState(PluginState state) {
throw new MustOverrideException();
}
@@ -1091,9 +1099,12 @@
* this should be viewed as a guide, not a hard limit. Setting the
* size to a value less than current database size does not cause the
* database to be trimmed. The default size is {@link Long#MAX_VALUE}.
+ * It is recommended to leave the maximum size set to the default value.
*
* @param appCacheMaxSize the maximum size in bytes
+ * @deprecated In future quota will be managed automatically.
*/
+ @Deprecated
public synchronized void setAppCacheMaxSize(long appCacheMaxSize) {
throw new MustOverrideException();
}
@@ -1224,7 +1235,9 @@
*
* @return the plugin state as a {@link PluginState} value
* @see #setPluginState
+ * @deprecated Plugins will not be supported in future, and should not be used.
*/
+ @Deprecated
public synchronized PluginState getPluginState() {
throw new MustOverrideException();
}
@@ -1329,7 +1342,10 @@
* {@link RenderPriority#NORMAL}.
*
* @param priority the priority
+ * @deprecated It is not recommended to adjust thread priorities, and this will
+ * not be supported in future versions.
*/
+ @Deprecated
public synchronized void setRenderPriority(RenderPriority priority) {
throw new MustOverrideException();
}
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index 1e955bd..7d9373c 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -171,7 +171,9 @@
* The quota is specified in bytes and the origin is specified using its string
* representation. Note that a quota is not enforced on a per-origin basis
* for the Application Cache API.
+ * @deprecated Controlling quota per-origin will not be supported in future.
*/
+ @Deprecated
public void setQuotaForOrigin(String origin, long quota) {
// Must be a no-op for backward compatibility: see the hidden constructor for reason.
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index dcb664e..175cbcb 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -208,8 +208,7 @@
* and default scaling is not applied to the web page; if the value is "1.5", then the device is
* considered a high density device (hdpi) and the page content is scaled 1.5x; if the
* value is "0.75", then the device is considered a low density device (ldpi) and the content is
- * scaled 0.75x. However, if you specify the {@code "target-densitydpi"} meta property
- * (discussed below), then you can stop this default scaling behavior.</li>
+ * scaled 0.75x.</li>
* <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
* densities for which this style sheet is to be used. The corresponding value should be either
* "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
@@ -219,29 +218,6 @@
* <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
* which is the high density pixel ratio.</p>
* </li>
- * <li>The {@code target-densitydpi} property for the {@code viewport} meta tag. You can use
- * this to specify the target density for which the web page is designed, using the following
- * values:
- * <ul>
- * <li>{@code device-dpi} - Use the device's native dpi as the target dpi. Default scaling never
- * occurs.</li>
- * <li>{@code high-dpi} - Use hdpi as the target dpi. Medium and low density screens scale down
- * as appropriate.</li>
- * <li>{@code medium-dpi} - Use mdpi as the target dpi. High density screens scale up and
- * low density screens scale down. This is also the default behavior.</li>
- * <li>{@code low-dpi} - Use ldpi as the target dpi. Medium and high density screens scale up
- * as appropriate.</li>
- * <li><em>{@code <value>}</em> - Specify a dpi value to use as the target dpi (accepted
- * values are 70-400).</li>
- * </ul>
- * <p>Here's an example meta tag to specify the target density:</p>
- * <pre><meta name="viewport" content="target-densitydpi=device-dpi" /></pre></li>
- * </ul>
- * <p>If you want to modify your web page for different densities, by using the {@code
- * -webkit-device-pixel-ratio} CSS media query and/or the {@code
- * window.devicePixelRatio} DOM property, then you should set the {@code target-densitydpi} meta
- * property to {@code device-dpi}. This stops Android from performing scaling in your web page and
- * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p>
*
* <h3>HTML5 Video support</h3>
*
@@ -602,7 +578,9 @@
* @param password the password for the given host
* @see WebViewDatabase#clearUsernamePassword
* @see WebViewDatabase#hasUsernamePassword
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
*/
+ @Deprecated
public void savePassword(String host, String username, String password) {
checkThread();
mProvider.savePassword(host, username, password);
@@ -999,7 +977,10 @@
/**
* Clears this WebView so that onDraw() will draw nothing but white background,
* and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
+ * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
+ * and release page resources (including any running JavaScript).
*/
+ @Deprecated
public void clearView() {
checkThread();
mProvider.clearView();
@@ -1389,7 +1370,11 @@
* @param showIme if true, show the IME, assuming the user will begin typing.
* If false and text is non-null, perform a find all.
* @return true if the find dialog is shown, false otherwise
+ * @deprecated This method does not work reliably on all Android versions;
+ * implementing a custom find dialog using WebView.findAllAsync()
+ * provides a more robust solution.
*/
+ @Deprecated
public boolean showFindDialog(String text, boolean showIme) {
checkThread();
return mProvider.showFindDialog(text, showIme);
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index f041f07..db20549 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -110,6 +110,7 @@
private int mCurWindowVisibility = View.VISIBLE;
+ private boolean mContentAnimations = true;
private boolean mHiddenByApp;
private boolean mHiddenBySystem;
private boolean mShowingForMode;
@@ -122,7 +123,7 @@
final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- if (mContentView != null) {
+ if (mContentAnimations && mContentView != null) {
mContentView.setTranslationY(0);
mTopVisibilityView.setTranslationY(0);
}
@@ -151,23 +152,24 @@
mActivity = activity;
Window window = activity.getWindow();
View decor = window.getDecorView();
- init(decor);
- if (!mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY)) {
+ boolean overlayMode = mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+ init(decor, overlayMode);
+ if (!overlayMode) {
mContentView = decor.findViewById(android.R.id.content);
}
}
public ActionBarImpl(Dialog dialog) {
mDialog = dialog;
- init(dialog.getWindow().getDecorView());
+ init(dialog.getWindow().getDecorView(), false);
}
- private void init(View decor) {
+ private void init(View decor, boolean overlayMode) {
mContext = decor.getContext();
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
com.android.internal.R.id.action_bar_overlay_layout);
if (mOverlayLayout != null) {
- mOverlayLayout.setActionBar(this);
+ mOverlayLayout.setActionBar(this, overlayMode);
}
mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
mContextView = (ActionBarContextView) decor.findViewById(
@@ -586,6 +588,10 @@
return mContainerView.getHeight();
}
+ public void enableContentAnimations(boolean enabled) {
+ mContentAnimations = enabled;
+ }
+
@Override
public void show() {
if (mHiddenByApp) {
@@ -684,7 +690,7 @@
AnimatorSet anim = new AnimatorSet();
AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView,
"translationY", 0));
- if (mContentView != null) {
+ if (mContentAnimations && mContentView != null) {
b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
startingY, 0));
}
@@ -709,7 +715,7 @@
} else {
mTopVisibilityView.setAlpha(1);
mTopVisibilityView.setTranslationY(0);
- if (mContentView != null) {
+ if (mContentAnimations && mContentView != null) {
mContentView.setTranslationY(0);
}
if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
@@ -742,7 +748,7 @@
}
AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView,
"translationY", endingY));
- if (mContentView != null) {
+ if (mContentAnimations && mContentView != null) {
b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
0, endingY));
}
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index 18a696e..482eba7 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -40,6 +40,7 @@
private ActionBarContainer mContainerView;
private ActionBarView mActionView;
private View mActionBarBottom;
+ private boolean mOverlayMode;
private int mLastSystemUiVisibility;
private final Rect mLocalInsets = new Rect();
@@ -63,8 +64,13 @@
ta.recycle();
}
- public void setActionBar(ActionBarImpl impl) {
+ public void setOverlayMode(boolean mode) {
+ mOverlayMode = mode;
+ }
+
+ public void setActionBar(ActionBarImpl impl, boolean overlayMode) {
mActionBar = impl;
+ mOverlayMode = overlayMode;
if (getWindowToken() != null) {
// This is being initialized after being added to a window;
// make sure to update all state now.
@@ -105,8 +111,13 @@
mLastSystemUiVisibility = visible;
final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0;
final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
+ final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
if (mActionBar != null) {
- if (barVisible) mActionBar.showForSystem();
+ // We want the bar to be visible if it is not being hidden,
+ // or the app has not turned on a stable UI mode (meaning they
+ // are performing explicit layout around the action bar).
+ mActionBar.enableContentAnimations(!stable);
+ if (barVisible || !stable) mActionBar.showForSystem();
else mActionBar.hideForSystem();
}
if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
@@ -161,35 +172,42 @@
changed |= applyInsets(mActionBarBottom, insets, true, false, true, true);
}
+ int topSpace = 0;
+ if (stable || mActionBarTop.getVisibility() == VISIBLE) {
+ // This is the space needed on top of the window for the action bar.
+ topSpace = mActionBarHeight;
+ }
+ if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
+ View tabs = mContainerView.getTabContainer();
+ if (tabs != null && (stable || tabs.getVisibility() == VISIBLE)) {
+ // If tabs are not embedded, increase space on top to account for them.
+ topSpace += mActionBarHeight;
+ }
+ }
+
+ int bottomSpace = 0;
+ if (mActionView.isSplitActionBar()) {
+ if ((mActionBarBottom != null
+ && (stable || mActionBarBottom.getVisibility() == VISIBLE))) {
+ // If action bar is split, adjust bottom insets for it.
+ bottomSpace = mActionBarHeight;
+ }
+ }
+
// If the window has not requested system UI layout flags, we need to
// make sure its content is not being covered by system UI... though it
// will still be covered by the action bar since they have requested it to
// overlay.
boolean res = computeFitSystemWindows(insets, mLocalInsets);
+ if (!mOverlayMode && !stable) {
+ mLocalInsets.top += topSpace;
+ mLocalInsets.bottom += bottomSpace;
+ } else {
+ insets.top += topSpace;
+ insets.bottom += bottomSpace;
+ }
changed |= applyInsets(mContent, mLocalInsets, true, true, true, true);
-
- if (stable || mActionBarTop.getVisibility() == VISIBLE) {
- // The action bar creates additional insets for its content to use.
- insets.top += mActionBarHeight;
- }
-
- if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
- View tabs = mContainerView.getTabContainer();
- if (stable || (tabs != null && tabs.getVisibility() == VISIBLE)) {
- // If tabs are not embedded, adjust insets to account for them.
- insets.top += mActionBarHeight;
- }
- }
-
- if (mActionView.isSplitActionBar()) {
- if (stable || (mActionBarBottom != null
- && mActionBarBottom.getVisibility() == VISIBLE)) {
- // If action bar is split, adjust bottom insets for it.
- insets.bottom += mActionBarHeight;
- }
- }
-
if (changed) {
requestLayout();
}
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 6410bc3..6640555 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -54,6 +54,21 @@
namespace android {
+class ClipCopier : public SkCanvas::ClipVisitor {
+public:
+ ClipCopier(SkCanvas* dstCanvas) : m_dstCanvas(dstCanvas) {}
+
+ virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) {
+ m_dstCanvas->clipRect(rect, op, antialias);
+ }
+ virtual void clipPath(const SkPath& path, SkRegion::Op op, bool antialias) {
+ m_dstCanvas->clipPath(path, op, antialias);
+ }
+
+private:
+ SkCanvas* m_dstCanvas;
+};
+
class SkCanvasGlue {
public:
@@ -68,13 +83,15 @@
static void copyCanvasState(JNIEnv* env, jobject clazz,
SkCanvas* srcCanvas, SkCanvas* dstCanvas) {
if (srcCanvas && dstCanvas) {
- if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) {
- dstCanvas->clipRegion(srcCanvas->getTotalClip());
- }
dstCanvas->setMatrix(srcCanvas->getTotalMatrix());
+ if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) {
+ ClipCopier copier(dstCanvas);
+ srcCanvas->replayClips(&copier);
+ }
}
}
+
static void freeCaches(JNIEnv* env, jobject) {
// these are called in no particular order
SkImageRef_GlobalPool::SetRAMUsed(0);
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 02e76e5..1ffb1b8 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -53,7 +53,6 @@
static struct {
jclass clazz;
jfieldID mNativeObject;
- jfieldID mGenerationId;
jfieldID mCanvas;
jmethodID ctor;
} gSurfaceClassInfo;
@@ -384,8 +383,6 @@
gSurfaceClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
gSurfaceClassInfo.mNativeObject =
env->GetFieldID(gSurfaceClassInfo.clazz, "mNativeObject", "I");
- gSurfaceClassInfo.mGenerationId =
- env->GetFieldID(gSurfaceClassInfo.clazz, "mGenerationId", "I");
gSurfaceClassInfo.mCanvas =
env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvas", "Landroid/graphics/Canvas;");
gSurfaceClassInfo.ctor = env->GetMethodID(gSurfaceClassInfo.clazz, "<init>", "(I)V");
diff --git a/core/jni/com_android_internal_os_ZygoteInit.cpp b/core/jni/com_android_internal_os_ZygoteInit.cpp
index bc8c4a7..44452f0 100644
--- a/core/jni/com_android_internal_os_ZygoteInit.cpp
+++ b/core/jni/com_android_internal_os_ZygoteInit.cpp
@@ -27,12 +27,8 @@
#include <JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"
-#include <linux/capability.h>
-#include <linux/prctl.h>
+#include <sys/capability.h>
#include <sys/prctl.h>
-extern "C" int capget(cap_user_header_t hdrp, cap_user_data_t datap);
-extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
-
namespace android {
diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
index f0b2313..95519c6 100644
--- a/core/res/res/layout/screen_action_bar.xml
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -18,38 +18,47 @@
This is an optimized layout for a screen with the Action Bar enabled.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.ActionBarOverlayLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/action_bar_overlay_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:fitsSystemWindows="true"
android:splitMotionEvents="false">
- <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="?android:attr/actionBarStyle">
- <com.android.internal.widget.ActionBarView
- android:id="@+id/action_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="?android:attr/actionBarStyle" />
- <com.android.internal.widget.ActionBarContextView
- android:id="@+id/action_context_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone"
- style="?android:attr/actionModeStyle" />
- </com.android.internal.widget.ActionBarContainer>
<FrameLayout android:id="@android:id/content"
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- android:foregroundGravity="fill_horizontal|top"
- android:foreground="?android:attr/windowContentOverlay" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ <LinearLayout android:id="@+id/top_action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top">
+ <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ style="?android:attr/actionBarStyle"
+ android:gravity="top">
+ <com.android.internal.widget.ActionBarView
+ android:id="@+id/action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/actionBarStyle" />
+ <com.android.internal.widget.ActionBarContextView
+ android:id="@+id/action_context_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ style="?android:attr/actionModeStyle" />
+ </com.android.internal.widget.ActionBarContainer>
+ <ImageView android:src="?android:attr/windowContentOverlay"
+ android:scaleType="fitXY"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
<com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
style="?android:attr/actionBarSplitStyle"
android:visibility="gone"
android:gravity="center"/>
-</LinearLayout>
+</com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/layout/screen_action_bar_overlay.xml b/core/res/res/layout/screen_action_bar_overlay.xml
deleted file mode 100644
index c8181d1..0000000
--- a/core/res/res/layout/screen_action_bar_overlay.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<!--
-This is an optimized layout for a screen with
-the Action Bar enabled overlaying application content.
--->
-
-<com.android.internal.widget.ActionBarOverlayLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/action_bar_overlay_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:splitMotionEvents="false">
- <FrameLayout android:id="@android:id/content"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- <LinearLayout android:id="@+id/top_action_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="top">
- <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- style="?android:attr/actionBarStyle"
- android:gravity="top">
- <com.android.internal.widget.ActionBarView
- android:id="@+id/action_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="?android:attr/actionBarStyle" />
- <com.android.internal.widget.ActionBarContextView
- android:id="@+id/action_context_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone"
- style="?android:attr/actionModeStyle" />
- </com.android.internal.widget.ActionBarContainer>
- <ImageView android:src="?android:attr/windowContentOverlay"
- android:scaleType="fitXY"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- </LinearLayout>
- <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- style="?android:attr/actionBarSplitStyle"
- android:visibility="gone"
- android:gravity="center"/>
-</com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index a64758c..8cfad68 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Altydaan-VPN koppel tans..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Altydaan-VPN gekoppel"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Altydaan-VPN-fout"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Raak om op te stel"</string>
<string name="upload_file" msgid="2897957172366730416">"Kies lêer"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Geen lêer gekies nie"</string>
<string name="reset" msgid="2448168080964209908">"Stel terug"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 2a23541..2cef636 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ሁልጊዜ የበራ VPN በመገናኘት ላይ…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ሁልጊዜ የበራ VPN ተገናኝቷል"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"ሁልጊዜ የበራ VPN ስህተት"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"ለማዋቀር ይንኩ"</string>
<string name="upload_file" msgid="2897957172366730416">"ፋይል ምረጥ"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ምንም ፋይል አልተመረጠም"</string>
<string name="reset" msgid="2448168080964209908">"ዳግም አስጀምር"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index cba7680..9f6345f 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"جارٍ الاتصال بشبكة ظاهرية خاصة (VPN) دائمة التشغيل..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"تم الاتصال بشبكة ظاهرية خاصة (VPN) دائمة التشغيل"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"خطأ بشبكة ظاهرية خاصة (VPN) دائمة التشغيل"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"المس للتهيئة"</string>
<string name="upload_file" msgid="2897957172366730416">"اختيار ملف"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"لم يتم اختيار أي ملف"</string>
<string name="reset" msgid="2448168080964209908">"إعادة تعيين"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index a2284ce..2c6aff3 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Падключэнне заўсёды ўключанага VPN..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Заўсёды ўключаны i падключаны VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Памылка заўсёды ўключанага VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Націсніце, каб змяніць налады"</string>
<string name="upload_file" msgid="2897957172366730416">"Выберыце файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Файл не выбраны"</string>
<string name="reset" msgid="2448168080964209908">"Скінуць"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index cc8ec59..f5f89a6 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Установява се връзка с винаги включената виртуална частна мрежа (VPN)…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Установена е връзка с винаги включената виртуална частна мрежа (VPN)"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Грешка във винаги включената виртуална частна мрежа (VPN)"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Докоснете, за да конфигурирате"</string>
<string name="upload_file" msgid="2897957172366730416">"Избор на файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Няма избран файл"</string>
<string name="reset" msgid="2448168080964209908">"Повторно задаване"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 4e8aa9c..b077308 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"T\'estàs connectant a la VPN sempre activada…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Estàs connectat a la VPN sempre activada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Error de la VPN sempre activada"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Toca per configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Trieu un fitxer"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No s\'ha escollit cap fitxer"</string>
<string name="reset" msgid="2448168080964209908">"Reinicia"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 76c17de..3482ed4f 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Připojování k trvalé síti VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Je připojena trvalá síť VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Chyba trvalé sítě VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Dotykem zahájíte konfiguraci"</string>
<string name="upload_file" msgid="2897957172366730416">"Zvolit soubor"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Není vybrán žádný soubor"</string>
<string name="reset" msgid="2448168080964209908">"Resetovat"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 6b01a68..3fb84ec 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Opretter forbindelse til Always-on VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN er forbundet"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Fejl i Always-on VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Tryk for at konfigurere"</string>
<string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
<string name="reset" msgid="2448168080964209908">"Nulstil"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 7fb0014..95e0b5b 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Verbindung zu durchgehend aktivem VPN wird hergestellt…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Mit durchgehend aktivem VPN verbunden"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Durchgehend aktives VPN – Verbindungsfehler"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Zum Konfigurieren berühren"</string>
<string name="upload_file" msgid="2897957172366730416">"Datei auswählen"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Keine ausgewählt"</string>
<string name="reset" msgid="2448168080964209908">"Zurücksetzen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 3307abf..478b064 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Σύνδεση πάντα ενεργοποιημένου VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Έχει συνδεθεί πάντα ενεργοποιημένο VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Σφάλμα πάντα ενεργοποιημένου VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Αγγίξτε για διαμόρφωση"</string>
<string name="upload_file" msgid="2897957172366730416">"Επιλογή αρχείου"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Δεν έχει επιλεγεί αρχείο"</string>
<string name="reset" msgid="2448168080964209908">"Επαναφορά"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index a695432..8794a5d 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Always-on VPN connecting…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN connected"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Always-on VPN error"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Touch to configure"</string>
<string name="upload_file" msgid="2897957172366730416">"Choose file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No file chosen"</string>
<string name="reset" msgid="2448168080964209908">"Reset"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 3aa900f..985b088 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Estableciendo conexión con la VPN siempre activada..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Se estableció conexión con la VPN siempre activada."</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Se produjo un error al establecer conexión con la VPN siempre activada."</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Toca para configurar."</string>
<string name="upload_file" msgid="2897957172366730416">"Elegir archivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"No se seleccionó un archivo."</string>
<string name="reset" msgid="2448168080964209908">"Restablecer"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 788c265c..febefe3 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Conectando VPN siempre activada…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN siempre activada conectada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Error de VPN siempre activada"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Toca para configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Seleccionar archivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Archivo no seleccionado"</string>
<string name="reset" msgid="2448168080964209908">"Restablecer"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 3312381e..2815c43 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Ühendamine alati sees VPN-iga …"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Ühendatud alati sees VPN-iga"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Alati sees VPN-i viga"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Puudutage seadistamiseks"</string>
<string name="upload_file" msgid="2897957172366730416">"Valige fail"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ühtegi faili pole valitud"</string>
<string name="reset" msgid="2448168080964209908">"Lähtesta"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index b6e3abe..660029a 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"در حال اتصال VPN همیشه فعال…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN همیشه فعال متصل شد"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"خطای VPN همیشه فعال"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"برای پیکربندی لمس کنید"</string>
<string name="upload_file" msgid="2897957172366730416">"انتخاب فایل"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"هیچ فایلی انتخاب نشد"</string>
<string name="reset" msgid="2448168080964209908">"بازنشانی"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 92712bc..78e1412 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Yhdistetään aina käytössä olevaan VPN-verkkoon..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Yhdistetty aina käytössä olevaan VPN-verkkoon"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Aina käytössä oleva VPN: virhe"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Kosketa ja tee määritykset"</string>
<string name="upload_file" msgid="2897957172366730416">"Valitse tiedosto"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ei valittua tiedostoa"</string>
<string name="reset" msgid="2448168080964209908">"Palauta"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 4c8ccc5..4c9c0a5 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN permanent en cours de connexion…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN permanent connecté"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Erreur du VPN permanent"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Appuyer pour configurer"</string>
<string name="upload_file" msgid="2897957172366730416">"Sélectionner un fichier"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Aucun fichier sélectionné"</string>
<string name="reset" msgid="2448168080964209908">"Réinitialiser"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 057f620..3aaace5 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"हमेशा-चालू VPN कनेक्ट हो रहा है…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"हमेशा-चालू VPN कनेक्ट है"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"हमेशा-चालू VPN त्रुटि"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"कॉन्फ़िगर करने के लिए स्पर्श करें"</string>
<string name="upload_file" msgid="2897957172366730416">"फ़ाइल चुनें"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"कोई फ़ाइल चुनी नहीं गई"</string>
<string name="reset" msgid="2448168080964209908">"रीसेट करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index cf566d7..dbf80ce 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Povezivanje s uvijek uključenom VPN mrežom…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Povezan s uvijek uključenom VPN mrežom"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Pogreška uvijek uključene VPN mreže"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Dodirnite za konfiguraciju"</string>
<string name="upload_file" msgid="2897957172366730416">"Odaberite datoteku"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nema odabranih datoteka"</string>
<string name="reset" msgid="2448168080964209908">"Ponovo postavi"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 02c80a6..3cb1df7 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Csatlakozás a mindig bekapcsolt VPN-hez..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Csatlakozva a mindig bekapcsolt VPN-hez"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Hiba a mindig bekapcsolt VPN-nel"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"A beállításhoz érintse meg"</string>
<string name="upload_file" msgid="2897957172366730416">"Fájl kiválasztása"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nincs fájl kiválasztva"</string>
<string name="reset" msgid="2448168080964209908">"Alaphelyzet"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index f0fd1ef..33e3b2d2 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Menyambungkan VPN selalu aktif..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN selalu aktif tersambung"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Kesalahan VPN selalu aktif"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Sentuh untuk mengonfigurasi"</string>
<string name="upload_file" msgid="2897957172366730416">"Pilih file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Tidak ada file yang dipilih"</string>
<string name="reset" msgid="2448168080964209908">"Setel ulang"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index ad9a94d..ec9a00e 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Connessione a VPN sempre attiva…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre attiva connessa"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Errore VPN sempre attiva"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Tocca per configurare"</string>
<string name="upload_file" msgid="2897957172366730416">"Scegli file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nessun file è stato scelto"</string>
<string name="reset" msgid="2448168080964209908">"Reimposta"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 9a2275b..6a0d9e2 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"ה-VPN שמופעל תמיד, מתחבר..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"ה-VPN שפועל תמיד, מחובר"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"שגיאת VPN שמופעל תמיד"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"גע כדי להגדיר"</string>
<string name="upload_file" msgid="2897957172366730416">"בחר קובץ"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"לא נבחר קובץ"</string>
<string name="reset" msgid="2448168080964209908">"איפוס"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index df3d6f7..5009af4 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPNに常時接続しています…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPNに常時接続しました"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"常時接続VPNのエラー"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"タップして設定してください"</string>
<string name="upload_file" msgid="2897957172366730416">"ファイルを選択"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ファイルが選択されていません"</string>
<string name="reset" msgid="2448168080964209908">"リセット"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 67192dd..46e6339 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"연결 유지 VPN에 연결하는 중…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"연결 유지 VPN에 연결됨"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"연결 유지 VPN 오류"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"설정하려면 터치하세요."</string>
<string name="upload_file" msgid="2897957172366730416">"파일 선택"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"파일을 선택하지 않았습니다."</string>
<string name="reset" msgid="2448168080964209908">"초기화"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 22656ff..fb476b5 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Prisijungiama prie visada įjungto VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Prisijungta prie visada įjungto VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Visada įjungto VPN klaida"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Jei norite konfigūruoti, palieskite."</string>
<string name="upload_file" msgid="2897957172366730416">"Pasirinkti failą"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nepasirinktas joks failas"</string>
<string name="reset" msgid="2448168080964209908">"Atstatyti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index f9b3381..667e29c 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Notiek savienojuma izveide ar vienmēr ieslēgtu VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Izveidots savienojums ar vienmēr ieslēgtu VPN."</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Kļūda saistībā ar vienmēr ieslēgtu VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Pieskarieties, lai konfigurētu."</string>
<string name="upload_file" msgid="2897957172366730416">"Izvēlēties failu"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Neviens fails nav izvēlēts"</string>
<string name="reset" msgid="2448168080964209908">"Atiestatīt"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index d9b39aa..a04aac1 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN sentiasa hidup sedang disambungkan..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sentiasa hidup telah disambungkan"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Ralat VPN sentiasa hidup"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Sentuh untuk mengkonfigurasikan"</string>
<string name="upload_file" msgid="2897957172366730416">"Pilih fail"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Tiada fail dipilih"</string>
<string name="reset" msgid="2448168080964209908">"Tetapkan semula"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 4e7eba8..62da0ae 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Alltid-på VPN kobler til ..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Alltid-på VPN er tilkoblet"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Alltid-på VPN-feil"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Trykk for å konfigurere"</string>
<string name="upload_file" msgid="2897957172366730416">"Velg fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
<string name="reset" msgid="2448168080964209908">"Tilbakestill"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 8af7e4c..47264b03 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Always-on VPN-verbinding maken…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Always-on VPN-verbinding"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Fout met Always-on VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Aanraken om te configureren"</string>
<string name="upload_file" msgid="2897957172366730416">"Bestand kiezen"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Geen bestand geselecteerd"</string>
<string name="reset" msgid="2448168080964209908">"Opnieuw instellen"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index e7bee92..98d7477 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Łączę z zawsze włączoną siecią VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Połączono z zawsze włączoną siecią VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Błąd zawsze włączonej sieci VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Kliknij, by skonfigurować"</string>
<string name="upload_file" msgid="2897957172366730416">"Wybierz plik"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nie wybrano pliku"</string>
<string name="reset" msgid="2448168080964209908">"Resetuj"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 552d24e..5a701a0 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"A ligar VPN sempre ativa..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre ativa ligada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Erro da VPN sempre ativa"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Tocar para configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Escolher ficheiro"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Não foi selecionado nenhum ficheiro"</string>
<string name="reset" msgid="2448168080964209908">"Repor"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index db894a0..9eb11b2 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"VPN sempre ativa conectando..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"VPN sempre ativa conectada"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Erro na VPN sempre ativa"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Toque para configurar"</string>
<string name="upload_file" msgid="2897957172366730416">"Escolher arquivo"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nenhum arquivo escolhido"</string>
<string name="reset" msgid="2448168080964209908">"Redefinir"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index fe7b437..eb7185f 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Se efectuează conectarea la reţeaua VPN activată permanent…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Conectat(ă) la reţeaua VPN activată permanent"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Eroare de reţea VPN activată permanent"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Atingeți pentru a configura"</string>
<string name="upload_file" msgid="2897957172366730416">"Alegeţi un fişier"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nu au fost găsite fişiere"</string>
<string name="reset" msgid="2448168080964209908">"Resetaţi"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 249e46b..b8b1c29 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Подключение…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Подключено"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Ошибка"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Нажмите, чтобы изменить настройки"</string>
<string name="upload_file" msgid="2897957172366730416">"Выбрать файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Не выбран файл"</string>
<string name="reset" msgid="2448168080964209908">"Сбросить"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index e11f93a..c1d2c46 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Prebieha pripájanie k vždy zapnutej sieti VPN..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Pripojenie k vždy zapnutej sieti VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Chyba vždy zapnutej siete VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Dotykom spustíte konfiguráciu"</string>
<string name="upload_file" msgid="2897957172366730416">"Zvoliť súbor"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nie je vybratý žiadny súbor"</string>
<string name="reset" msgid="2448168080964209908">"Obnoviť"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 5a3f897..7eb8583 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Povezovanje v stalno vklopljeno navidezno zasebno omrežje ..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Vzpostavljena povezava v stalno vklopljeno navidezno zasebno omrežje"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Napaka stalno vklopljenega navideznega zasebnega omrežja"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Dotaknite se, če želite konfigurirati"</string>
<string name="upload_file" msgid="2897957172366730416">"Izberi datoteko"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Nobena datoteka ni izbrana"</string>
<string name="reset" msgid="2448168080964209908">"Ponastavi"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 2072c7e..de942ea 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Повезивање стално укљученог VPN-а..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Стално укључени VPN је повезан"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Грешка стално укљученог VPN-а"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Додирните да бисте конфигурисали"</string>
<string name="upload_file" msgid="2897957172366730416">"Одабери датотеку"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Није изабрана ниједна датотека"</string>
<string name="reset" msgid="2448168080964209908">"Поново постави"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 1b4cba5..adfc22a 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Ansluter till Always-on VPN ..."</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Ansluten till Always-on VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Fel på Always-on VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Tryck om du vill konfigurera"</string>
<string name="upload_file" msgid="2897957172366730416">"Välj fil"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil har valts"</string>
<string name="reset" msgid="2448168080964209908">"Återställ"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index a678e1f..f30d11f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Kila mara VPN iliyowashwa inaunganishwa…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Kila mara VPN iliyowashwa imeunganishwa"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Kila mara kuna hitilafu ya VPN iliyowashwa"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Gusa ili kusanidi"</string>
<string name="upload_file" msgid="2897957172366730416">"Chagua faili"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Hakuna faili iliyochaguliwa"</string>
<string name="reset" msgid="2448168080964209908">"Weka upya"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 01a9fec..1a6d610 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"กำลังเชื่อมต่อ VPN แบบเปิดตลอดเวลา…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"เชื่อมต่อ VPN แบบเปิดตลอดเวลาแล้ว"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"ข้อผิดพลาดของ VPN แบบเปิดตลอดเวลา"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"แตะเพื่อกำหนดค่า"</string>
<string name="upload_file" msgid="2897957172366730416">"เลือกไฟล์"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"ไม่ได้เลือกไฟล์ไว้"</string>
<string name="reset" msgid="2448168080964209908">"รีเซ็ต"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 4de813a..1354129 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Kumukonekta ang Always-on VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Nakakonekta ang Always-on VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Error sa Always-on VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Pindutin upang i-configure"</string>
<string name="upload_file" msgid="2897957172366730416">"Pumili ng file"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Walang napiling file"</string>
<string name="reset" msgid="2448168080964209908">"I-reset"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index cfc3c7b..aa3244c 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Her zaman açık VPN\'ye bağlanılıyor…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Her zaman açık VPN\'ye bağlanıldı"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Her zaman açık VPN hatası"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Yapılandırmak için dokunun"</string>
<string name="upload_file" msgid="2897957172366730416">"Dosya seç"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Seçili dosya yok"</string>
<string name="reset" msgid="2448168080964209908">"Sıfırla"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 095c364..2f6d65c 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Під’єднання до постійної мережі VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Під’єднано до постійної мережі VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Помилка постійної мережі VPN"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Натисніть, щоб налаштувати"</string>
<string name="upload_file" msgid="2897957172366730416">"Виберіть файл"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Не вибрано файл"</string>
<string name="reset" msgid="2448168080964209908">"Віднов."</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 2918928..df86597 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"Đang kết nối VPN luôn bật…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"Đã kết nối VPN luôn bật"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Lỗi VPN luôn bật"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Chạm để định cấu hình"</string>
<string name="upload_file" msgid="2897957172366730416">"Chọn tệp"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Không có tệp nào được chọn"</string>
<string name="reset" msgid="2448168080964209908">"Đặt lại"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index dcfc35f..554c1aa 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"正在连接到始终开启的 VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"已连接到始终开启的 VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"始终开启的 VPN 出现错误"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"触摸即可进行配置"</string>
<string name="upload_file" msgid="2897957172366730416">"选择文件"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"未选定任何文件"</string>
<string name="reset" msgid="2448168080964209908">"重置"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 2ceb440..c13cdf7 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"正在連線至永久連線的 VPN…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"已連線至永久連線的 VPN"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"永久連線的 VPN 發生錯誤"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"輕觸即可設定"</string>
<string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"未選擇任何檔案"</string>
<string name="reset" msgid="2448168080964209908">"重設"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 01cadb7..6dc9e68 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1269,8 +1269,7 @@
<string name="vpn_lockdown_connecting" msgid="6443438964440960745">"I-VPN ehlala ikhanya iyaxhuma…"</string>
<string name="vpn_lockdown_connected" msgid="8202679674819213931">"I-VPN ehlala ikhanya ixhunyiwe"</string>
<string name="vpn_lockdown_error" msgid="6009249814034708175">"Iphutha le-VPN ehlala ikhanya"</string>
- <!-- no translation found for vpn_lockdown_config (6415899150671537970) -->
- <skip />
+ <string name="vpn_lockdown_config" msgid="6415899150671537970">"Thinta ukuze ulungiselele"</string>
<string name="upload_file" msgid="2897957172366730416">"Khetha ifayela"</string>
<string name="no_file_chosen" msgid="6363648562170759465">"Ayikho ifayela ekhethiwe"</string>
<string name="reset" msgid="2448168080964209908">"Setha kabusha"</string>
diff --git a/docs/html/training/articles/security-ssl.jd b/docs/html/training/articles/security-ssl.jd
new file mode 100644
index 0000000..9a6320b
--- /dev/null
+++ b/docs/html/training/articles/security-ssl.jd
@@ -0,0 +1,539 @@
+page.title=Security with HTTPS and SSL
+page.article=true
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>In this document</h2>
+<ol class="nolist">
+ <li><a href="#Concepts">Concepts</a></li>
+ <li><a href="#HttpsExample">An HTTP Example</a></li>
+ <li><a href="#CommonProblems">Common Problems Verifying Server Certificates</a>
+ <ol class="nolist">
+ <li><a href="#UnknownCa">Unknown certificate authority</a></li>
+ <li><a href="#SelfSigned">Self-signed server certificate</a></li>
+ <li><a href="#MissingCa">Missing intermediate certificate authority</a></li>
+ </ol>
+ </li>
+ <li><a href="#CommonHostnameProbs">Common Problems with Hostname Verification</a></li>
+ <li><a href="#WarningsSslSocket">Warnings About Using SSLSocket Directly</a></li>
+ <li><a href="#Blacklisting">Blacklisting</a></li>
+ <li><a href="#Pinning">Pinning</a></li>
+ <li><a href="#ClientCert">Client Certificates</a></li>
+</ol>
+
+
+<h2>See also</h2>
+<ul>
+<li><a href="http://source.android.com/tech/security/index.html">Android
+Security Overview</a></li>
+<li><a href="{@docRoot}guide/topics/security/permissions.html">Permissions</a></li>
+</ul>
+</div></div>
+
+
+
+<p>The Secure Sockets Layer (SSL)—now technically known as <a
+href="http://en.wikipedia.org/wiki/Transport_Layer_Security">Transport Layer Security
+(TLS)</a>—is a
+common building block for encrypted communications between clients and servers. It's possible that
+an application might use SSL incorrectly such that malicious entities may
+be able to intercept an app's data over the network. To help you ensure that this does not happen
+to your app, this article highlights the common pitfalls when using secure network protocols and addresses some larger concerns about using <a
+href="http://en.wikipedia.org/wiki/Public-key_infrastructure">Public-Key Infrastructure (PKI)</a>.
+
+
+<h2 id="Concepts">Concepts</h2>
+
+<p>In a typical SSL usage scenario, a server is configured with a certificate containing a
+public key as well as a matching private key. As part of the handshake between an SSL client
+and server, the server proves it has the private key by signing its certificate with <a
+href="http://en.wikipedia.org/wiki/Public-key_cryptography">public-key cryptography</a>.</p>
+
+<p>However, anyone can generate their own certificate and private key, so a simple handshake
+doesn't prove anything about the server other than that the server knows the private key that
+matches the public key of the certificate. One way to solve this problem is to have the client
+have a set of one or more certificates it trusts. If the certificate is not in the set, the
+server is not to be trusted.</p>
+
+<p>There are several downsides to this simple approach. Servers should be able to
+upgrade to stronger keys over time ("key rotation"), which replaces the public key in the
+certificate with a new one. Unfortunately, now the client app has to be updated due to what
+is essentially a server configuration change. This is especially problematic if the server
+is not under the app developer's control, for example if it is a third party web service. This
+approach also has issues if the app has to talk to arbitrary servers such as a web browser or
+email app.</p>
+
+<p>In order to address these downsides, servers are typically configured with certificates
+from well known issuers called <a
+href="http://en.wikipedia.org/wiki/Certificate_authority">Certificate Authorities (CAs)</a>.
+The host platform generally contains a list of well known CAs that it trusts.
+As of Android 4.2 (Jelly Bean), Android currently contains over 100 CAs that are updated
+in each release. Similar to a server, a CA has a certificate and a private key. When issuing
+a certificate for a server, the CA <a
+href="http://en.wikipedia.org/wiki/Digital_signature">signs</a>
+the server certificate using its private key. The
+client can then verify that the server has a certificate issued by a CA known to the platform.</p>
+
+<p>However, while solving some problems, using CAs introduces another. Because the CA issues
+certificates for many servers, you still need some way to make sure you are talking to the
+server you want. To address this, the certificate issued by the CA identifies the server
+either with a specific name such as <em>gmail.com</em> or a wildcarded set of
+hosts such as <em>*.google.com</em>. </p>
+
+<p>The following example will make these concepts a little more concrete. In the snippet below
+from a command line, the <a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a>
+tool's {@code s_client} command looks at Wikipedia's server certificate information. It
+specifies port 443 because that is the default for <acronym title="Hypertext Transfer
+Protocol Secure">HTTPS</acronym>. The command sends
+the output of {@code openssl s_client} to {@code openssl x509}, which formats information
+about certificates according to the <a
+href="http://en.wikipedia.org/wiki/X.509">X.509 standard</a>. Specifically,
+the command asks for the subject, which contains the server name information,
+and the issuer, which identifies the CA.</p>
+
+<pre class="no-pretty-print">
+$ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
+<b>subject=</b> /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/<b>CN=*.wikipedia.org</b>
+<b>issuer=</b> /C=US/O=GeoTrust, Inc./CN=<b>RapidSSL CA</b>
+</pre>
+
+<p>You can see that the certificate was issued for servers matching <em>*.wikipedia.org</em> by
+the RapidSSL CA.</p>
+
+
+
+<h2 id="HttpsExample">An HTTPS Example</h2>
+
+<p>Assuming you have a web server with a
+certificate issued by a well known CA, you can make a secure request with code as
+simple this:</p>
+
+<pre>
+URL url = new URL("https://wikipedia.org");
+URLConnection urlConnection = url.openConnection();
+InputStream in = urlConnection.getInputStream();
+copyInputStreamToOutputStream(in, System.out);
+</pre>
+
+<p>Yes, it really can be that simple. If you want to tailor the HTTP request, you can cast to
+an {@link java.net.HttpURLConnection}. The Android documentation for
+{@link java.net.HttpURLConnection} has further examples about how to deal with request
+and response headers, posting content, managing cookies, using proxies, caching responses,
+and so on. But in terms of the details for verifying certificates and hostnames, the Android
+framework takes care of it for you through these APIs.
+This is where you want to be if at all possible. That said, below are some other considerations.</p>
+
+
+
+<h2 id="CommonProblems">Common Problems Verifying Server Certificates</h2>
+
+<p>Suppose instead of receiving the content from {@link java.net.URLConnection#getInputStream
+getInputStream()}, it throws an exception:</p>
+
+<pre class="no-pretty-print">
+javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
+ at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
+ at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
+ at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
+ at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
+ at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
+ at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
+ at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
+ at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
+ at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
+</pre>
+
+<p>This can happen for several reasons, including:
+<ol>
+ <li><a href="#UnknownCa">The CA that issued the server certificate was unknown</a></li>
+ <li><a href="#SelfSigned">The server certificate wasn't signed by a CA, but was self signed</a></li>
+ <li><a href="#MissingCa">The server configuration is missing an intermediate CA</a></li>
+</ol>
+
+<p>The following sections discuss how to address these problems while keeping your
+connection to the server secure.
+
+
+
+<h3 id="UnknownCa">Unknown certificate authority</h3>
+
+<p>In this case, the {@link javax.net.ssl.SSLHandshakeException} occurs
+because you have a CA that isn't trusted by the system. It could be because
+you have a certificate from a new CA that isn't yet trusted by Android or your app is
+running on an older version without the CA. More often a CA is unknown because it isn't a
+public CA, but a private one issued by an organization such as a government, corporation,
+or education institution for their own use.</p>
+
+<p>Fortunately, you can teach {@link javax.net.ssl.HttpsURLConnection}
+to trust a specific set of CAs. The procedure
+can be a little convoluted, so below is an example that takes a specific CA from
+an {@link java.io.InputStream}, uses it to create a {@link java.security.KeyStore},
+which is then used to create and initialize a
+{@link javax.net.ssl.TrustManager}. A {@link javax.net.ssl.TrustManager} is what the system
+uses to validate certificates from the server
+and—by creating one from a {@link java.security.KeyStore} with one or more CAs—those
+will be the only CAs trusted by that {@link javax.net.ssl.TrustManager}.</p>
+
+<p>Given the new {@link javax.net.ssl.TrustManager},
+the example initializes a new {@link javax.net.ssl.SSLContext} which provides
+an {@link javax.net.ssl.SSLSocketFactory} you can use to override the default
+{@link javax.net.ssl.SSLSocketFactory} from
+{@link javax.net.ssl.HttpsURLConnection}. This way the
+connection will use your CAs for certificate validation.</p>
+
+<p>Here is the example in
+full using an organizational CA from the University of Washington:</p>
+
+<pre>
+// Load CAs from an InputStream
+// (could be from a resource or ByteArrayInputStream or ...)
+CertificateFactory cf = CertificateFactory.getInstance("X.509");
+// From https://www.washington.edu/itconnect/security/ca/load-der.crt
+InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
+Certificate ca;
+try {
+ ca = cf.generateCertificate(caInput);
+ System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
+} finally {
+ caInput.close();
+}
+
+// Create a KeyStore containing our trusted CAs
+String keyStoreType = KeyStore.getDefaultType();
+KeyStore keyStore = KeyStore.getInstance(keyStoreType);
+keyStore.load(null, null);
+keyStore.setCertificateEntry("ca", ca);
+
+// Create a TrustManager that trusts the CAs in our KeyStore
+String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
+tmf.init(keyStore);
+
+// Create an SSLContext that uses our TrustManager
+SSLContext context = SSLContext.getInstance("TLS");
+context.init(null, tmf.getTrustManagers(), null);
+
+// Tell the URLConnection to use a SocketFactory from our SSLContext
+URL url = new URL("https://certs.cac.washington.edu/CAtest/");
+HttpsURLConnection urlConnection =
+ (HttpsURLConnection)url.openConnection();
+urlConnection.setSSLSocketFactory(context.getSocketFactory());
+InputStream in = urlConnection.getInputStream();
+copyInputStreamToOutputStream(in, System.out);
+</pre>
+
+<p>With a custom {@link javax.net.ssl.TrustManager} that knows about your CAs,
+the system is able to validate
+that your server certificate come from a trusted issuer.</p>
+
+<p class="caution"><strong>Caution:</strong>
+Many web sites describe a poor alternative solution which is to install a
+{@link javax.net.ssl.TrustManager} that does nothing. If you do this you might as well not
+be encrypting your communication, because anyone can attack your users at a public Wi-Fi hotspot
+by using <acronym title="Domain Name System">DNS</acronym> tricks to send your users'
+traffic through a proxy of their own that pretends to be your server. The attacker can then
+record passwords and other personal data. This works because the attacker can generate a
+certificate and—without a {@link javax.net.ssl.TrustManager} that actually
+validates that the certificate comes from a trusted
+source—your app could be talking to anyone. So don't do this, not even temporarily. You can
+always make your app trust the issuer of the server's certificate, so just do it.</p>
+
+
+
+<h3 id="SelfSigned">Self-signed server certificate</h3>
+
+<p>The second case of {@link javax.net.ssl.SSLHandshakeException} is
+due to a self-signed certificate, which means the server is behaving as its own CA.
+This is similar to an unknown certificate authority, so you can use the
+same approach from the previous section.</p>
+
+<p>You can create yout own {@link javax.net.ssl.TrustManager},
+this time trusting the server certificate directly. This has all of the
+downsides discussed earlier of tying your app directly to a certificate, but can be done
+securely. However, you should be careful to make sure your self-signed certificate has a
+reasonably strong key. As of 2012, a 2048-bit RSA signature with an exponent of 65537 expiring
+yearly is acceptable. When rotating keys, you should check for <a
+href="http://csrc.nist.gov/groups/ST/key_mgmt/index.html">recommendations</a> from an
+authority (such as <a href="http://www.nist.gov/">NIST</a>) about what is acceptable.</p>
+
+
+
+<h3 id="MissingCa">Missing intermediate certificate authority</h3>
+
+<p>The third case of {@link javax.net.ssl.SSLHandshakeException}
+occurs due to a missing intermediate CA. Most public
+CAs don't sign server certificates directly. Instead, they use their main CA certificate,
+referred to as the root CA, to sign intermediate CAs. They do this so the root CA can be stored
+offline to reduce risk of compromise. However, operating systems like Android typically
+trust only root CAs directly, which leaves a short gap of trust between the server
+certificate—signed by the intermediate CA—and the certificate verifier,
+which knows the root CA. To solve
+this, the server doesn't send the client only it's certificate during the SSL handshake, but
+a chain of certificates from the server CA through any intermediates necessary to reach a
+trusted root CA.</p>
+
+<p>To see what this looks like in practice, here's the <em>mail.google.com</em> certificate
+chain as viewed by the <a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a>
+{@code s_client} command:</p>
+
+<pre class="no-pretty-print">
+$ openssl s_client -connect mail.google.com:443
+---
+Certificate chain
+ 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com
+ i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
+ 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
+ i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
+---
+</pre>
+
+
+<p>This shows that the server sends a certificate for <em>mail.google.com</em>
+issued by the <em>Thawte SGC</em> CA, which is an intermediate CA, and a second certificate
+for the <em>Thawte SGC</em> CA issued by a <em>Verisign</em> CA, which is the primary CA that's
+trusted by Android.</p>
+
+<p>However, it is not uncommon to configure a server to not include the necessary
+intermediate CA. For example, here is a server that can cause an error in Android browsers and
+exceptions in Android apps:</p>
+
+<pre class="no-pretty-print">
+$ openssl s_client -connect egov.uscis.gov:443
+---
+Certificate chain
+ 0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov
+ i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3
+---
+</pre>
+
+<p>What is interesting to note here is that visiting this server in most desktop browsers
+does not cause an error like a completely unknown CA or self-signed server certificate would
+cause. This is because most desktop browsers cache trusted intermediate CAs over time. Once
+a browser has visited and learned about an intermediate CA from one site, it won't
+need to have the intermediate CA included in the certificate chain the next time.</p>
+
+<p>Some sites do this intentionally for secondary web servers used to serve resources. For
+example, they might have their main HTML page served by a server with a full certificate
+chain, but have servers for resources such as images, CSS, or JavaScript not include the
+CA, presumably to save bandwidth. Unfortunately, sometimes these servers might be providing
+a web service you are trying to call from your Android app, which is not as forgiving.</p>
+
+<p>There are two approaches to solve this issue:</p>
+<ul>
+ <li>Configure the server to
+ include the intermediate CA in the server chain. Most CAs provide documentation on how to do
+ this for all common web servers. This is the only approach if you need the site to work with
+ default Android browsers at least through Android 4.2.</li>
+ <li>Or, treat the
+ intermediate CA like any other unknown CA, and create a {@link javax.net.ssl.TrustManager}
+ to trust it directly, as done in the previous two sections.</li>
+</ul>
+
+
+<h2 id="CommonHostnameProbs">Common Problems with Hostname Verification</h2>
+
+<p>As mentioned at the beginning of this article,
+there are two key parts to verifying an SSL connection. The first
+is to verify the certificate is from a trusted source, which was the focus of the previous
+section. The focus of this section is the second part: making sure the server you are
+talking to presents the right certificate. When it doesn't, you'll typically see an error
+like this:</p>
+
+<pre class="no-pretty-print">
+java.io.IOException: Hostname 'example.com' was not verified
+ at libcore.net.http.HttpConnection.verifySecureSocketHostname(HttpConnection.java:223)
+ at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:446)
+ at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
+ at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
+ at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
+ at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
+ at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)
+</pre>
+
+
+<p>One reason this can happen is due to a server configuration error. The server is
+configured with a certificate that does not have a subject or subject alternative name fields
+that match the server you are trying to reach. It is possible to have one certificate be used
+with many different servers. For example, looking at the <em>google.com</em> certificate with
+<a href="http://www.openssl.org/docs/apps/openssl.html">{@code openssl}</a> {@code
+s_client -connect google.com:443 | openssl x509 -text} you can see that a subject
+that supports <em>*.google.com</em> but also subject alternative names for <em>*.youtube.com</em>,
+<em>*.android.com</em>, and others. The error occurs only when the server name you
+are connecting to isn't listed by the certificate as acceptable.</p>
+
+<p>Unfortunately this can happen for another reason as well: <a
+href="http://en.wikipedia.org/wiki/Virtual_hosting">virtual hosting</a>. When sharing a
+server for more than one hostname with HTTP, the web server can tell from the HTTP/1.1 request
+which target hostname the client is looking for. Unfortunately this is complicated with
+HTTPS, because the server has to know which certificate to return before it sees the HTTP
+request. To address this problem, newer versions of SSL, specifically TLSv.1.0 and later,
+support <a href="http://en.wikipedia.org/wiki/Server_Name_Indication">Server Name Indication
+(SNI)</a>, which allows the SSL client to specify the intended
+hostname to the server so the proper certificate can be returned.</p>
+
+<p>Fortunately, {@link javax.net.ssl.HttpsURLConnection} supports
+SNI since Android 2.3. Unfortunately, Apache
+HTTP Client does not, which is one of the many reasons we discourage its use. One workaround
+if you need to support Android 2.2 (and older) or Apache HTTP Client is to set up an alternative
+virtual host on a unique port so that it's unambiguous which server certificate to return.</p>
+
+<p>The more drastic alternative is to replace {@link javax.net.ssl.HostnameVerifier}
+with one that uses not the
+hostname of your virtual host, but the one returned by the server by default.</p>
+
+<p class="caution"><strong>Caution:</strong> Replacing {@link javax.net.ssl.HostnameVerifier}
+can be <strong>very dangerous</strong> if the other virtual host is
+not under your control, because a man-in-the-middle attack could direct traffic to another
+server without your knowledge.</p>
+
+<p>If you are still sure you want to override hostname verification, here is an example
+that replaces the verifier for a single {@link java.net.URLConnection}
+with one that still verifies that the hostname is at least on expected by the app:</p>
+
+<pre>
+// Create an HostnameVerifier that hardwires the expected hostname.
+// Note that is different than the URL's hostname:
+// example.com versus example.org
+HostnameVerifier hostnameVerifier = new HostnameVerifier() {
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ HostnameVerifier hv =
+ HttpsURLConnection.getDefaultHostnameVerifier();
+ return hv.verify("example.com", session);
+ }
+};
+
+// Tell the URLConnection to use our HostnameVerifier
+URL url = new URL("https://example.org/");
+HttpsURLConnection urlConnection =
+ (HttpsURLConnection)url.openConnection();
+urlConnection.setHostnameVerifier(hostnameVerifier);
+InputStream in = urlConnection.getInputStream();
+copyInputStreamToOutputStream(in, System.out);
+</pre>
+
+<p>But remember, if you find yourself replacing hostname verification, especially
+due to virtual hosting, it's still <strong>very dangerous</strong> if the other virtual host is
+not under your control and you should find an alternative hosting arrangement
+that avoids this issue.</p>
+
+
+
+
+<h2 id="WarningsSslSocket">Warnings About Using SSLSocket Directly</h2>
+
+<p>So far, the examples have focused on HTTPS using {@link javax.net.ssl.HttpsURLConnection}.
+Sometimes apps need to use SSL separate from HTTP. For example, an email app might use SSL variants
+of SMTP, POP3, or IMAP. In those cases, the app would want to use {@link javax.net.ssl.SSLSocket}
+directly, much the same way that {@link javax.net.ssl.HttpsURLConnection} does internally.</p>
+
+<p>The techniques described so
+far to deal with certificate verification issues also apply to {@link javax.net.ssl.SSLSocket}.
+In fact, when using a custom {@link javax.net.ssl.TrustManager}, what is passed to
+{@link javax.net.ssl.HttpsURLConnection} is an {@link javax.net.ssl.SSLSocketFactory}.
+So if you need to use a custom {@link javax.net.ssl.TrustManager} with an
+{@link javax.net.ssl.SSLSocket}, follow
+the same steps and use that {@link javax.net.ssl.SSLSocketFactory} to create your
+{@link javax.net.ssl.SSLSocket}.</p>
+
+<p class="caution"><strong>Caution:</strong>
+{@link javax.net.ssl.SSLSocket} <strong>does not</strong> perform hostname verification. It is
+up the your app to do its own hostname verification, preferably by calling {@link
+javax.net.ssl.HttpsURLConnection#getDefaultHostnameVerifier()} with the expected hostname. Further
+beware that {@link javax.net.ssl.HostnameVerifier#verify HostnameVerifier.verify()}
+doesn't throw an exception on error but instead returns a boolean result that you must
+explicitly check.</p>
+
+<p>Here is an example showing how you can do this. It shows that when connecting to
+<em>gmail.com</em> port 443 without SNI support, you'll receive a certificate for
+<em>mail.google.com</em>. This is expected in this case, so check to make sure that
+the certificate is indeed for <em>mail.google.com</em>:</p>
+
+<pre>
+// Open SSLSocket directly to gmail.com
+SocketFactory sf = SSLSocketFactory.getDefault();
+SSLSocket socket = (SSLSocket) sf.createSocket("gmail.com", 443);
+HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
+SSLSession s = socket.getSession();
+
+// Verify that the certicate hostname is for mail.google.com
+// This is due to lack of SNI support in the current SSLSocket.
+if (!hv.verify("mail.google.com", s)) {
+ throw new SSLHandshakeException("Expected mail.google.com, "
+ "found " + s.getPeerPrincipal());
+}
+
+// At this point SSLSocket performed certificate verificaiton and
+// we have performed hostname verification, so it is safe to proceed.
+
+// ... use socket ...
+socket.close();
+</pre>
+
+
+
+<h2 id="Blacklisting">Blacklisting</h2>
+
+<p>SSL relies heavily on CAs to issue certificates to only the properly verified owners
+of servers and domains. In rare cases, CAs are either tricked or, in the case of <a
+href="http://en.wikipedia.org/wiki/Comodo_Group#Breach_of_security">Comodo</a> or <a
+href="http://en.wikipedia.org/wiki/DigiNotar">DigiNotar</a>, breached,
+resulting in the certificates for a hostname to be issued to
+someone other than the owner of the server or domain.</p>
+
+<p>In order to mitigate this risk, Android has the ability to blacklist certain certificates or even
+whole CAs. While this list was historically built into the operating system, starting in
+Android 4.2 this list can be remotely updated to deal with future compromises.</p>
+
+
+
+<h2 id="Pinning">Pinning</h2>
+
+<p>An app can further protect itself from fraudulently issued certificates by a
+technique known as pinning. This is basically using the example provided in the unknown CA case
+above to restrict an app's trusted CAs to a small set known to be used by the app's servers. This
+prevents the compromise of one of the other 100+ CAs in the system from resulting in a breach of
+the apps secure channel.</p>
+
+
+
+<h2 id="ClientCert">Client Certificates</h2>
+
+<p>This article has focused on the user of SSL to secure communications with servers. SSL also
+supports the notion of client certificates that allow the server to validate the identity of a
+client. While beyond the scope of this article, the techniques involved are similar to specifying
+a custom {@link javax.net.ssl.TrustManager}.
+See the discussion about creating a custom {@link javax.net.ssl.KeyManager} in the documentation for
+{@link javax.net.ssl.HttpsURLConnection}.</p>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 9518046..79980be 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -1069,6 +1069,13 @@
"How to perform various tasks and keep your app's data and your user's data secure."
>Security Tips</a>
</li>
+
+ <li>
+ <a href="<?cs var:toroot ?>training/articles/security-ssl.html"
+ description=
+ "How to ensure that your app is secure when performing network transactions."
+ >Security with HTTPS and SSL</a>
+ </li>
<li class="nav-section">
<div class="nav-section-header">
diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index 2ec1293..1dbcddb 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -217,16 +217,19 @@
int prevAlpha = paint.getAlpha();
paint.setAlpha(modulateAlpha(prevAlpha, mShapeState.mAlpha));
- if (mShapeState.mShape != null) {
- // need the save both for the translate, and for the (unknown) Shape
- int count = canvas.save();
- canvas.translate(r.left, r.top);
- onDraw(mShapeState.mShape, canvas, paint);
- canvas.restoreToCount(count);
- } else {
- canvas.drawRect(r, paint);
+ // only draw shape if it may affect output
+ if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadow) {
+ if (mShapeState.mShape != null) {
+ // need the save both for the translate, and for the (unknown) Shape
+ int count = canvas.save();
+ canvas.translate(r.left, r.top);
+ onDraw(mShapeState.mShape, canvas, paint);
+ canvas.restoreToCount(count);
+ } else {
+ canvas.drawRect(r, paint);
+ }
}
-
+
// restore
paint.setAlpha(prevAlpha);
}
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index a99cdad..b8564b6 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -26,6 +26,7 @@
import android.graphics.SurfaceTexture;
import android.util.Log;
import android.util.TypedValue;
+import android.graphics.Canvas;
/**
* <p>
@@ -429,6 +430,9 @@
private void validateBitmapFormat(Bitmap b) {
Bitmap.Config bc = b.getConfig();
+ if (bc == null) {
+ throw new RSIllegalArgumentException("Bitmap has an unsupported format for this operation");
+ }
switch (bc) {
case ALPHA_8:
if (mType.getElement().mKind != Element.DataKind.PIXEL_A) {
@@ -612,6 +616,13 @@
*/
public void copyFrom(Bitmap b) {
mRS.validate();
+ if (b.getConfig() == null) {
+ Bitmap newBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(newBitmap);
+ c.drawBitmap(b, 0, 0, null);
+ copyFrom(newBitmap);
+ return;
+ }
validateBitmapSize(b);
validateBitmapFormat(b);
mRS.nAllocationCopyFromBitmap(getID(mRS), b);
@@ -951,6 +962,12 @@
*/
public void copy2DRangeFrom(int xoff, int yoff, Bitmap data) {
mRS.validate();
+ if (data.getConfig() == null) {
+ Bitmap newBitmap = Bitmap.createBitmap(data.getWidth(), data.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(newBitmap);
+ c.drawBitmap(data, 0, 0, null);
+ copy2DRangeFrom(xoff, yoff, newBitmap);
+ }
validateBitmapFormat(data);
validate2DRange(xoff, yoff, data.getWidth(), data.getHeight());
mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, data);
@@ -1220,6 +1237,18 @@
MipmapControl mips,
int usage) {
rs.validate();
+
+ // WAR undocumented color formats
+ if (b.getConfig() == null) {
+ if ((usage & USAGE_SHARED) != 0) {
+ throw new RSIllegalArgumentException("USAGE_SHARED cannot be used with a Bitmap that has a null config.");
+ }
+ Bitmap newBitmap = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(newBitmap);
+ c.drawBitmap(b, 0, 0, null);
+ return createFromBitmap(rs, newBitmap, mips, usage);
+ }
+
Type t = typeFromBitmap(rs, b, mips);
// enable optimized bitmap path only with no mipmap and script-only usage
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 9db8fe8..9ecfb5a 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1140,9 +1140,20 @@
const float* positions, SkPaint* paint, float length)
: DrawBoundedOp(paint), mText(text), mBytesCount(bytesCount), mCount(count),
mX(x), mY(y), mPositions(positions), mLength(length) {
+ // duplicates bounds calculation from OpenGLRenderer::drawText, but doesn't alter mX
SkPaint::FontMetrics metrics;
paint->getFontMetrics(&metrics, 0.0f);
- mLocalBounds.set(mX, mY + metrics.fTop, mX + length, mY + metrics.fBottom);
+ switch (paint->getTextAlign()) {
+ case SkPaint::kCenter_Align:
+ x -= length / 2.0f;
+ break;
+ case SkPaint::kRight_Align:
+ x -= length;
+ break;
+ default:
+ break;
+ }
+ mLocalBounds.set(x, mY + metrics.fTop, x + length, mY + metrics.fBottom);
}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 710f12f..16218fa 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -406,7 +406,7 @@
if (addDrawOp(op)) {
// precache if draw operation is visible
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+ fontRenderer.precache(paint, text, count, mat4::identity());
}
return DrawGlInfo::kStatusDone;
}
@@ -423,7 +423,7 @@
if (addDrawOp(op)) {
// precache if draw operation is visible
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+ fontRenderer.precache(paint, text, count, mat4::identity());
}
return DrawGlInfo::kStatusDone;
}
@@ -442,7 +442,9 @@
if (addDrawOp(op)) {
// precache if draw operation is visible
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+ const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+ fontRenderer.precache(paint, text, count,
+ pureTranslate ? mat4::identity() : *mSnapshot->transform);
}
return DrawGlInfo::kStatusDone;
}
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index db65b88..d5ea0f9 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -180,7 +180,17 @@
void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
checkInit();
+
+ // If the glyph bitmap is empty let's assum the glyph is valid
+ // so we can avoid doing extra work later on
+ if (glyph.fWidth == 0 || glyph.fHeight == 0) {
+ cachedGlyph->mIsValid = true;
+ cachedGlyph->mCacheTexture = NULL;
+ return;
+ }
+
cachedGlyph->mIsValid = false;
+
// If the glyph is too tall, don't cache it
if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index fb77ef6..7e9734f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2151,17 +2151,17 @@
alpha *= mSnapshot->alpha;
- mCaches.activeTexture(0);
- Texture* texture = mCaches.textureCache.get(bitmap);
- if (!texture) return DrawGlInfo::kStatusDone;
- const AutoTexture autoCleanup(texture);
- texture->setWrap(GL_CLAMP_TO_EDGE, true);
- texture->setFilter(GL_LINEAR, true);
-
const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(),
right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors);
if (CC_LIKELY(mesh && mesh->verticesCount > 0)) {
+ mCaches.activeTexture(0);
+ Texture* texture = mCaches.textureCache.get(bitmap);
+ if (!texture) return DrawGlInfo::kStatusDone;
+ const AutoTexture autoCleanup(texture);
+ texture->setWrap(GL_CLAMP_TO_EDGE, true);
+ texture->setFilter(GL_LINEAR, true);
+
const bool pureTranslate = mSnapshot->transform->isPureTranslate();
// Mark the current layer dirty where we are going to draw the patch
if (hasLayer() && mesh->hasEmptyQuads) {
@@ -2666,6 +2666,7 @@
const float oldX = x;
const float oldY = y;
const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+ const bool isPerspective = mSnapshot->transform->isPerspective();
if (CC_LIKELY(pureTranslate)) {
x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
@@ -2687,8 +2688,7 @@
fontRenderer.setFont(paint, pureTranslate ? mat4::identity() : *mSnapshot->transform);
// Pick the appropriate texture filtering
- bool linearFilter = !mSnapshot->transform->isPureTranslate() ||
- fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
+ bool linearFilter = !pureTranslate || fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
// The font renderer will always use texture unit 0
mCaches.activeTexture(0);
@@ -2701,13 +2701,13 @@
setupDrawShader();
setupDrawBlending(true, mode);
setupDrawProgram();
- setupDrawModelView(x, y, x, y, true, true);
+ setupDrawModelView(x, y, x, y, !isPerspective, true);
// See comment above; the font renderer must use texture unit 0
// assert(mTextureUnit == 0)
setupDrawTexture(fontRenderer.getTexture(linearFilter));
setupDrawPureColorUniforms();
setupDrawColorFilterUniforms();
- setupDrawShaderUniforms(true);
+ setupDrawShaderUniforms(!isPerspective);
setupDrawTextGammaUniforms();
const Rect* clip = mSnapshot->hasPerspectiveTransform() ? NULL : mSnapshot->clipRect;
@@ -2727,6 +2727,9 @@
}
if (status && hasActiveLayer) {
+ if (isPerspective) {
+ mSnapshot->transform->mapRect(bounds);
+ }
dirtyLayerUnchecked(bounds, getRegion());
}
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 13ee336..e8b6d47 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -24,6 +24,7 @@
#include <SkXfermode.h>
+#include "Debug.h"
#include "Matrix.h"
#include "Properties.h"
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 0f014cb..f78fb2d 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -433,6 +433,12 @@
Program* ProgramCache::get(const ProgramDescription& description) {
programid key = description.key();
+ if (key == (PROGRAM_KEY_TEXTURE | PROGRAM_KEY_A8_TEXTURE)) {
+ // program for A8, unmodulated, texture w/o shader (black text/path textures) is equivalent
+ // to standard texture program (bitmaps, patches). Consider them equivalent.
+ key = PROGRAM_KEY_TEXTURE;
+ }
+
ssize_t index = mCache.indexOfKey(key);
Program* program = NULL;
if (index < 0) {
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 6cfe0c7..1ca148d 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -31,17 +31,6 @@
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
-// Defines
-///////////////////////////////////////////////////////////////////////////////
-
-// Debug
-#if DEBUG_PROGRAMS
- #define PROGRAM_LOGD(...) ALOGD(__VA_ARGS__)
-#else
- #define PROGRAM_LOGD(...)
-#endif
-
-///////////////////////////////////////////////////////////////////////////////
// Cache
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index ea9fd03..d48b612 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -52,6 +52,7 @@
mStyle = paint->getStyle();
mStrokeWidth = paint->getStrokeWidth();
mAntiAliasing = paint->isAntiAlias();
+ mLookupTransform.reset();
mLookupTransform[SkMatrix::kMScaleX] = matrix.data[mat4::kScaleX];
mLookupTransform[SkMatrix::kMScaleY] = matrix.data[mat4::kScaleY];
mLookupTransform[SkMatrix::kMSkewX] = matrix.data[mat4::kSkewX];
@@ -165,7 +166,7 @@
void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
float nPenX = x + glyph->mBitmapLeft;
- float nPenY = y + (glyph->mBitmapTop + glyph->mBitmapHeight);
+ float nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
float width = (float) glyph->mBitmapWidth;
float height = (float) glyph->mBitmapHeight;
@@ -181,6 +182,38 @@
nPenX, nPenY - height, u1, v1, glyph->mCacheTexture);
}
+void Font::drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+ uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
+ SkMatrix i;
+ if (!mDescription.mLookupTransform.invert(&i)) {
+ return;
+ }
+
+ SkPoint p[4];
+ p[0].set(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight);
+ p[1].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop + glyph->mBitmapHeight);
+ p[2].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop);
+ p[3].set(glyph->mBitmapLeft, glyph->mBitmapTop);
+
+ i.mapPoints(p, 4);
+
+ p[0].offset(x, y);
+ p[1].offset(x, y);
+ p[2].offset(x, y);
+ p[3].offset(x, y);
+
+ float u1 = glyph->mBitmapMinU;
+ float u2 = glyph->mBitmapMaxU;
+ float v1 = glyph->mBitmapMinV;
+ float v2 = glyph->mBitmapMaxV;
+
+ mState->appendRotatedMeshQuad(
+ p[0].fX, p[0].fY, u1, v2,
+ p[1].fX, p[1].fY, u2, v2,
+ p[2].fX, p[2].fY, u2, v1,
+ p[3].fX, p[3].fY, u1, v1, glyph->mCacheTexture);
+}
+
void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
int nPenX = x + glyph->mBitmapLeft;
@@ -307,7 +340,7 @@
penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
prevRsbDelta = cachedGlyph->mRsbDelta;
- if (cachedGlyph->mIsValid) {
+ if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent);
}
@@ -328,7 +361,6 @@
}
void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
-
if (numGlyphs == 0 || text == NULL) {
return;
}
@@ -357,14 +389,18 @@
static RenderGlyph gRenderGlyph[] = {
&android::uirenderer::Font::drawCachedGlyph,
+ &android::uirenderer::Font::drawCachedGlyphPerspective,
&android::uirenderer::Font::drawCachedGlyphBitmap,
+ &android::uirenderer::Font::drawCachedGlyphBitmap,
+ &android::uirenderer::Font::measureCachedGlyph,
&android::uirenderer::Font::measureCachedGlyph
};
- RenderGlyph render = gRenderGlyph[mode];
+ RenderGlyph render = gRenderGlyph[(mode << 1) + mTransform.isPerspective()];
text += start;
int glyphsCount = 0;
+ const bool applyTransform = !mTransform.isIdentity() && !mTransform.isPerspective();
const SkPaint::Align align = paint->getTextAlign();
while (glyphsCount < numGlyphs) {
@@ -377,12 +413,13 @@
CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
- // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
- if (cachedGlyph->mIsValid) {
+ // If it's still not valid, we couldn't cache it, so we shouldn't
+ // draw garbage; also skip empty glyphs (spaces)
+ if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
float penX = x + positions[(glyphsCount << 1)];
float penY = y + positions[(glyphsCount << 1) + 1];
- if (!mTransform.isIdentity()) {
+ if (applyTransform) {
mTransform.mapPoint(penX, penY);
}
@@ -424,15 +461,18 @@
glyph->mBitmapWidth = skiaGlyph.fWidth;
glyph->mBitmapHeight = skiaGlyph.fHeight;
- uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
- uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
+ bool empty = skiaGlyph.fWidth == 0 || skiaGlyph.fHeight == 0;
+ if (!empty) {
+ uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
+ uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
- glyph->mBitmapMinU = startX / (float) cacheWidth;
- glyph->mBitmapMinV = startY / (float) cacheHeight;
- glyph->mBitmapMaxU = endX / (float) cacheWidth;
- glyph->mBitmapMaxV = endY / (float) cacheHeight;
+ glyph->mBitmapMinU = startX / (float) cacheWidth;
+ glyph->mBitmapMinV = startY / (float) cacheHeight;
+ glyph->mBitmapMaxU = endX / (float) cacheWidth;
+ glyph->mBitmapMaxV = endY / (float) cacheHeight;
- mState->setTextureDirty();
+ mState->setTextureDirty();
+ }
}
CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) {
@@ -440,8 +480,8 @@
mCachedGlyphs.add(glyph, newGlyph);
const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform);
- newGlyph->mGlyphIndex = skiaGlyph.fID;
newGlyph->mIsValid = false;
+ newGlyph->mGlyphIndex = skiaGlyph.fID;
updateGlyphCache(paint, skiaGlyph, newGlyph, precaching);
@@ -452,14 +492,13 @@
FontDescription description(paint, matrix);
Font* font = state->mActiveFonts.get(description);
- if (font) {
- font->mTransform.load(matrix);
- return font;
+ if (!font) {
+ font = new Font(state, description);
+ state->mActiveFonts.put(description, font);
}
+ font->mTransform.load(matrix);
- Font* newFont = new Font(state, description);
- state->mActiveFonts.put(description, newFont);
- return newFont;
+ return font;
}
}; // namespace uirenderer
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 26b10afd..542b552 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -124,6 +124,9 @@
void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
Rect* bounds, const float* pos);
+ void drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+ Rect* bounds, const float* pos);
void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
Rect* bounds, const float* pos);
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index e1d9b73..5ad305c 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -33,6 +33,7 @@
import com.android.internal.view.menu.MenuView;
import com.android.internal.widget.ActionBarContainer;
import com.android.internal.widget.ActionBarContextView;
+import com.android.internal.widget.ActionBarOverlayLayout;
import com.android.internal.widget.ActionBarView;
import android.app.KeyguardManager;
@@ -2788,11 +2789,7 @@
com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
- if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
- layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
- } else {
- layoutResource = com.android.internal.R.layout.screen_action_bar;
- }
+ layoutResource = com.android.internal.R.layout.screen_action_bar;
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 5e4855b..3257d2c 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -3382,6 +3382,7 @@
try {
if (tracker != null) {
mNetd.setFirewallEnabled(true);
+ mNetd.setFirewallInterfaceRule("lo", true);
mLockdownTracker = tracker;
mLockdownTracker.init();
} else {
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 5630b08..7686705 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -23,6 +23,7 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_TETHERING;
+import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult;
@@ -122,6 +123,7 @@
public static final int QuotaCounterResult = 220;
public static final int TetheringStatsResult = 221;
public static final int DnsProxyQueryResult = 222;
+ public static final int ClatdStatusResult = 223;
public static final int InterfaceChange = 600;
public static final int BandwidthControl = 601;
@@ -1498,6 +1500,43 @@
}
}
+ @Override
+ public void startClatd(String interfaceName) throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("clatd", "start", interfaceName);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void stopClatd() throws IllegalStateException {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ try {
+ mConnector.execute("clatd", "stop");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public boolean isClatdStarted() {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ final NativeDaemonEvent event;
+ try {
+ event = mConnector.execute("clatd", "status");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+
+ event.checkCode(ClatdStatusResult);
+ return event.getMessage().endsWith("started");
+ }
+
/** {@inheritDoc} */
@Override
public void monitor() {
diff --git a/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java b/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java
index 30a968f..6633787 100644
--- a/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java
+++ b/tests/FrameworkPerf/src/com/android/frameworkperf/FrameworkPerfActivity.java
@@ -272,7 +272,7 @@
args.bgOp = mCurOpIndex;
} else {
args.fgOp = mCurOpIndex;
- args.bgOp = mFgTestIndex;
+ args.bgOp = mBgTestIndex;
}
}
Bundle bundle = new Bundle();
@@ -424,6 +424,8 @@
updateWakeLock();
stopService(new Intent(this, SchedulerService.class));
synchronized (mResults) {
+ Log.i("PerfRes", "\tTEST\tFgOps\tFgMsPerOp\tFgTime\tFgName\tBgOps\tBgMsPerOp\t"
+ + "BgTime\tBgName");
for (int i=0; i<mResults.size(); i++) {
RunResult result = mResults.get(i);
float fgMsPerOp = result.getFgMsPerOp();
diff --git a/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java b/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java
index a8c43e9..5f4f006 100644
--- a/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java
+++ b/tests/FrameworkPerf/src/com/android/frameworkperf/TestService.java
@@ -300,7 +300,7 @@
threadFinished(false);
}
}, Process.THREAD_PRIORITY_BACKGROUND);
- mForegroundThread = new RunnerThread("background", new Runnable() {
+ mForegroundThread = new RunnerThread("foreground", new Runnable() {
@Override public void run() {
boolean running;
int ops = 0;
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 7c7d10e..46a539e 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -30,448 +30,466 @@
android:label="HwUi"
android:hardwareAccelerated="true">
- <meta-data android:name="android.graphics.renderThread" android:value="true" />
+ <activity
+ android:name="HwTests"
+ android:label="OpenGL Renderer Tests">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
<activity
android:name="ScaledTextActivity"
- android:label="_ScaledText"
+ android:label="Text/Scaled"
android:theme="@android:style/Theme.Holo.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="Rotate3dTextActivity"
+ android:label="Text/3D Rotation"
+ android:theme="@android:style/Theme.Holo.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="NoAATextActivity"
- android:label="_NoAAText">
+ android:label="Text/Aliased">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ScaledPathsActivity"
- android:label="_ScaledPaths">
+ android:label="Path/Scaled">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="Alpha8BitmapActivity"
- android:label="_Alpha8Bitmap">
+ android:label="Bitmaps/Alpha8">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="MipMapActivity"
- android:label="_MipMap">
+ android:label="Bitmaps/MipMap">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="PathOffsetActivity"
- android:label="_PathOffset">
+ android:label="Path/Offset">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="MultiLayersActivity"
- android:label="_MultiLayers">
+ android:label="Layers/Multiple">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TJunctionActivity"
- android:label="_T-Junction">
+ android:label="Layers/T-Junction">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TextPathActivity"
- android:label="_TextPath">
+ android:label="Text/As Path">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="GradientStopsActivity"
- android:label="_GradientStops">
+ android:label="Gradients/Multi-stops">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="PaintDrawFilterActivity"
- android:label="_DrawFilter">
+ android:label="Paint/Draw Filter">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="BigGradientActivity"
- android:label="_BigGradient">
+ android:label="Gradients/Large">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="DatePickerActivity"
- android:label="_DatePicker">
+ android:label="View/DatePicker">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ClipRegionActivity"
- android:label="_ClipRegion">
+ android:label="Clip/Region 1">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ClipRegion2Activity"
- android:label="_ClipRegion2">
+ android:label="Clip/Region 2">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ClipRegion3Activity"
- android:label="_ClipRegion3">
+ android:label="Clip/Region 3">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="DisplayListLayersActivity"
- android:label="__DisplayListLayers">
+ android:label="Layers/Display Lists">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="MatrixActivity"
- android:label="_Matrix">
+ android:label="Misc/Matrix">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TextFadeActivity"
- android:label="_TextFade">
+ android:label="Text/Fade">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="MaxBitmapSizeActivity"
- android:label="_MaxBitmapSize">
+ android:label="Bitmaps/Max Size">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TimeDialogActivity"
- android:label="_TimeDialog">
+ android:label="View/TimeDialog">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="OpaqueActivity"
- android:label="_Opaque">
+ android:label="Window/Opaque">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="GetBitmapActivity"
- android:label="_GetBitmap">
+ android:label="TextureView/Get Bitmap">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="SmallCircleActivity"
- android:label="_SmallCircle">
+ android:label="Draw/Small Circle">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ClearActivity"
- android:label="_Clear">
+ android:label="Window/Clear">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TextureViewActivity"
- android:label="_TextureView">
+ android:label="TextureView/Camera">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="GlyphCacheActivity"
- android:label="_GlyphCache">
+ android:label="Text/Glyph Cache">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="CanvasTextureViewActivity"
- android:label="_CanvasTextureView">
+ android:label="TextureView/Canvas">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="GLTextureViewActivity"
- android:label="_TextureViewGL">
+ android:label="TextureView/OpenGL">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="BitmapMeshActivity"
- android:label="_BitmapMesh">
+ android:label="Bitmaps/Mesh">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="BitmapMutateActivity"
- android:label="_BitmapMutate">
+ android:label="Bitmaps/Mutate">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="BitmapMeshLayerActivity"
- android:label="_BitmapMeshLayer">
+ android:label="Bitmaps/Mesh in Layer">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="MarqueeActivity"
- android:label="_Marquee">
+ android:label="Text/Marquee">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ShapesActivity"
- android:label="_Shapes">
+ android:label="Path/Shapes">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ColoredRectsActivity"
- android:label="_Rects">
+ android:label="Draw/Rects">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="SimplePatchActivity"
- android:label="_SimplePatch"
+ android:label="Draw/9-Patch"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ViewLayersActivity"
- android:label="_ViewLayers">
+ android:label="Layers/Views 1">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ViewLayersActivity2"
- android:label="_ViewLayers2">
+ android:label="Layers/Views 2">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ViewLayersActivity3"
- android:label="_ViewLayers3">
+ android:label="Layers/Views 3">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ViewLayersActivity4"
- android:label="_ViewLayers4">
+ android:label="Layers/Views 4">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ViewLayersActivity5"
- android:label="_ViewLayers5">
+ android:label="Layers/Views 5">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="AlphaLayersActivity"
- android:label="_αLayers">
+ android:label="Layers/Alpha">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="AdvancedGradientsActivity"
- android:label="_Advanced Gradients">
+ android:label="Gradients/Advanced">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="Bitmaps3dActivity"
- android:label="_Bitmaps3d">
+ android:label="Bitmaps/3D Rotation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="LabelsActivity"
- android:label="_Labels">
+ android:label="View/TextView">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ViewFlipperActivity"
- android:label="_ViewFlipper"
+ android:label="View/ViewFlipper"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ResizeActivity"
- android:label="_Resize"
+ android:label="Window/Resize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TextGammaActivity"
- android:label="_Gamma"
+ android:label="Text/Gamma"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TextGammaActivity$SubGammaActivity"
- android:label="_Sub Gamma"
+ android:label="Text/Sub Gamma"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:hardwareAccelerated="false">
<intent-filter>
@@ -481,333 +499,333 @@
<activity
android:name="LayersActivity"
- android:label="_Layers"
+ android:label="Layers/Canvas Layers"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="NewLayersActivity"
- android:label="_NewLayers">
+ android:label="Layers/Overlapping Layers">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="XfermodeActivity"
- android:label="_Xfermodes"
+ android:label="Draw/Xfermodes"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="BitmapsActivity"
- android:label="_Bitmaps"
+ android:label="Bitmaps/Draw Bitmaps"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="BitmapsSkewActivity"
- android:label="_BitmapsSkew">
+ android:label="Bitmaps/Skew">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="BitmapsAlphaActivity"
- android:label="_BitmapsAlpha"
+ android:label="Bitmaps/Alpha"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="BitmapsRectActivity"
- android:label="_BitmapsRect"
+ android:label="Bitmaps/Rect"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ThinPatchesActivity"
- android:label="_9patchThin"
+ android:label="Draw/9-Patch Thin Drawable"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="NinePatchesActivity"
- android:label="_9patch">
+ android:label="Draw/9-Patch Drawable">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="MoreNinePatchesActivity"
- android:label="_9patch2">
+ android:label="Draw/9-Patch Vertical Drawable">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="QuickRejectActivity"
- android:label="_QuickReject">
+ android:label="Clip/QuickReject">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="RotationActivity"
- android:label="_Rotation">
+ android:label="View/Rotation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="GradientsActivity"
- android:label="_Gradients">
+ android:label="Gradients/Gradients">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ShadersActivity"
- android:label="_Shaders">
+ android:label="Shaders/Shaders">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TextActivity"
- android:label="_Text"
+ android:label="Text/Simple Text"
android:theme="@android:style/Theme.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="PosTextActivity"
- android:label="_PosText"
+ android:label="Text/Pos Text"
android:theme="@android:style/Theme.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ListActivity"
- android:label="__List">
+ android:label="View/List">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TransparentListActivity"
- android:label="_TransparentList">
+ android:label="View/Transparent List">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="MoreShadersActivity"
- android:label="_Shaders2">
+ android:label="Shaders/Compose Shaders">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ColorFiltersActivity"
- android:label="_ColorFilters">
+ android:label="ColorFilters/Filters">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="LinesActivity"
- android:label="_Lines">
+ android:label="Draw/Lines">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="Lines2Activity"
- android:label="_Lines2">
+ android:label="Draw/Lines 2">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="PathsActivity"
- android:label="_Paths">
+ android:label="Path/Paths">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TextOnPathActivity"
- android:label="_TextOnPath">
+ android:label="Text/Text on Path">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="PathsCacheActivity"
- android:label="_PathsCache">
+ android:label="Path/Cache">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="PointsActivity"
- android:label="_Points">
+ android:label="Draw/Points">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="Transform3dActivity"
- android:label="_3d">
+ android:label="Draw/3D Transform">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="Animated3dActivity"
- android:label="_Animated 3d">
+ android:label="Draw/Animated 3D Transform">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="SimplePathsActivity"
- android:label="_SimplePaths">
+ android:label="Path/Simple Paths">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="AdvancedBlendActivity"
- android:label="_Advanced Blend">
+ android:label="Draw/Advanced Blend">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="FramebufferBlendActivity"
- android:label="_FramebufferBlend">
+ android:label="Draw/Framebuffer Blend">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="StackActivity"
- android:label="_Stacks">
+ android:label="View/Stacks">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="PathDestructionActivity"
- android:label="_PathDestruction">
+ android:label="Path/Path Destruction">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="TransformsAndAnimationsActivity"
- android:label="_TransformAnim">
+ android:label="Draw/Transforms and Animations">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ViewPropertyAlphaActivity"
- android:label="_ViewPropAlpha">
+ android:label="View/Alpha Property">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="ViewLayerInvalidationActivity"
- android:label="_ViewLayerInvalidation">
+ android:label="Layers/Invalidation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/HwTests.java b/tests/HwAccelerationTest/src/com/android/test/hwui/HwTests.java
new file mode 100644
index 0000000..b1c32a8
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/HwTests.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.*;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("UnusedDeclaration")
+public class HwTests extends android.app.ListActivity {
+ private static final String EXTRA_PATH = "com.android.test.hwui.Path";
+ private static final String CATEGORY_HWUI_TEST = "com.android.test.hwui.TEST";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ String path = intent.getStringExtra("com.android.test.hwui.Path");
+
+ if (path == null) {
+ path = "";
+ }
+
+ setListAdapter(new SimpleAdapter(this, getData(path),
+ android.R.layout.simple_list_item_1, new String[] { "title" },
+ new int[] { android.R.id.text1 }));
+ getListView().setTextFilterEnabled(true);
+ }
+
+ protected List<Map<String, Object>> getData(String prefix) {
+ List<Map<String, Object>> myData = new ArrayList<Map<String, Object>>();
+
+ Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+ mainIntent.addCategory(CATEGORY_HWUI_TEST);
+
+ PackageManager pm = getPackageManager();
+ List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);
+
+ if (null == list)
+ return myData;
+
+ String[] prefixPath;
+ String prefixWithSlash = prefix;
+
+ if (prefix.equals("")) {
+ prefixPath = null;
+ } else {
+ prefixPath = prefix.split("/");
+ prefixWithSlash = prefix + "/";
+ }
+
+ int len = list.size();
+
+ Map<String, Boolean> entries = new HashMap<String, Boolean>();
+
+ for (int i = 0; i < len; i++) {
+ ResolveInfo info = list.get(i);
+ CharSequence labelSeq = info.loadLabel(pm);
+ String label = labelSeq != null
+ ? labelSeq.toString()
+ : info.activityInfo.name;
+
+ if (prefixWithSlash.length() == 0 || label.startsWith(prefixWithSlash)) {
+
+ String[] labelPath = label.split("/");
+
+ String nextLabel = prefixPath == null ? labelPath[0] : labelPath[prefixPath.length];
+
+ if ((prefixPath != null ? prefixPath.length : 0) == labelPath.length - 1) {
+ addItem(myData, nextLabel, activityIntent(
+ info.activityInfo.applicationInfo.packageName,
+ info.activityInfo.name));
+ } else {
+ if (entries.get(nextLabel) == null) {
+ addItem(myData, nextLabel, browseIntent(prefix.equals("") ?
+ nextLabel : prefix + "/" + nextLabel));
+ entries.put(nextLabel, true);
+ }
+ }
+ }
+ }
+
+ Collections.sort(myData, sDisplayNameComparator);
+
+ return myData;
+ }
+
+ private final static Comparator<Map<String, Object>> sDisplayNameComparator =
+ new Comparator<Map<String, Object>>() {
+ private final Collator collator = Collator.getInstance();
+
+ public int compare(Map<String, Object> map1, Map<String, Object> map2) {
+ return collator.compare(map1.get("title"), map2.get("title"));
+ }
+ };
+
+ protected Intent activityIntent(String pkg, String componentName) {
+ Intent result = new Intent();
+ result.setClassName(pkg, componentName);
+ return result;
+ }
+
+ protected Intent browseIntent(String path) {
+ Intent result = new Intent();
+ result.setClass(this, HwTests.class);
+ result.putExtra(EXTRA_PATH, path);
+ return result;
+ }
+
+ protected void addItem(List<Map<String, Object>> data, String name, Intent intent) {
+ Map<String, Object> temp = new HashMap<String, Object>();
+ temp.put("title", name);
+ temp.put("intent", intent);
+ data.add(temp);
+ }
+
+ @Override
+ @SuppressWarnings({ "unchecked", "UnusedParameters" })
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ Map<String, Object> map = (Map<String, Object>)l.getItemAtPosition(position);
+
+ Intent intent = (Intent) map.get("intent");
+ startActivity(intent);
+ }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java
new file mode 100644
index 0000000..93b8705
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class Rotate3dTextActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Rotate3dTextView view = new Rotate3dTextView(this);
+ setContentView(view);
+ }
+
+ public static class Rotate3dTextView extends View {
+ private static final String TEXT = "Hello libhwui! ";
+
+ private final Paint mPaint;
+
+ public Rotate3dTextView(Context c) {
+ super(c);
+
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setTextSize(50.0f);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+
+ setRotationY(45.0f);
+ setScaleX(2.0f);
+ setScaleY(2.0f);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ canvas.drawText(TEXT, getWidth() / 2.0f, getHeight() / 2.0f, mPaint);
+
+ invalidate();
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 9e77b8c..95d3041 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -210,13 +210,14 @@
* age=2623
* flags=[WPA2-PSK-CCMP][WPS][ESS]
* ssid=zubyb
+ * ====
*
* RANGE=ALL gets all scan results
* RANGE=ID- gets results from ID
* MASK=<N> see wpa_supplicant/src/common/wpa_ctrl.h for details
*/
public String scanResults(int sid) {
- return doStringCommand("BSS RANGE=" + sid + "- MASK=0x1987");
+ return doStringCommand("BSS RANGE=" + sid + "- MASK=0x21987");
}
public boolean startDriver() {
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 28f6bb2..900a38a 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1378,7 +1378,7 @@
private static final String FLAGS_STR = "flags=";
private static final String SSID_STR = "ssid=";
private static final String DELIMITER_STR = "====";
- private static final int SCAN_BUF_RANGE = 3900;
+ private static final String END_STR = "####";
/**
* Format:
@@ -1417,11 +1417,12 @@
if (TextUtils.isEmpty(tmpResults)) break;
scanResultsBuf.append(tmpResults);
scanResultsBuf.append("\n");
- if (tmpResults.length() < SCAN_BUF_RANGE) break;
String[] lines = tmpResults.split("\n");
sid = -1;
for (int i=lines.length - 1; i >= 0; i--) {
- if (lines[i].startsWith(ID_STR)) {
+ if (lines[i].startsWith(END_STR)) {
+ break;
+ } else if (lines[i].startsWith(ID_STR)) {
try {
sid = Integer.parseInt(lines[i].substring(ID_STR.length())) + 1;
} catch (NumberFormatException e) {
@@ -1472,7 +1473,7 @@
} else if (line.startsWith(SSID_STR)) {
wifiSsid = WifiSsid.createFromAsciiEncoded(
line.substring(SSID_STR.length()));
- } else if (line.startsWith(DELIMITER_STR)) {
+ } else if (line.startsWith(DELIMITER_STR) || line.startsWith(END_STR)) {
if (bssid != null) {
String ssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
String key = bssid + ssid;