Merge "Fix RTL layout in Recent Apps, landscape mode" 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 e65f622..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";
@@ -16032,7 +16033,7 @@
method public static void resetThreadGcInvocationCount();
method public static deprecated int setAllocationLimit(int);
method public static deprecated int setGlobalAllocationLimit(int);
- method public static void startAllocCounting();
+ method public static deprecated void startAllocCounting();
method public static void startMethodTracing();
method public static void startMethodTracing(java.lang.String);
method public static void startMethodTracing(java.lang.String, int);
@@ -18904,6 +18905,7 @@
field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS";
field public static final java.lang.String ACTION_WIRELESS_SETTINGS = "android.settings.WIRELESS_SETTINGS";
field public static final java.lang.String AUTHORITY = "settings";
+ field public static final java.lang.String EXTRA_ACCOUNT_TYPES = "account_types";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_INPUT_METHOD_ID = "input_method_id";
}
@@ -27455,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);
@@ -27465,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);
}
@@ -27504,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();
@@ -27527,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);
@@ -27550,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);
@@ -27630,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 {
@@ -27673,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);
@@ -27720,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>);
@@ -27736,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/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 06ef472..97c7ff3e 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -49,6 +49,8 @@
private final Object mLock = new Object();
+ private final Binder mToken = new Binder();
+
private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
private IAccessibilityServiceClient mClient;
@@ -164,7 +166,7 @@
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
- manager.registerUiTestAutomationService(client, info);
+ manager.registerUiTestAutomationService(mToken, client, info);
mClient = client;
} catch (RemoteException re) {
throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
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 e9b800d..8aef405 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -16,6 +16,7 @@
package android.content;
+import static android.content.pm.PackageManager.GET_PROVIDERS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.app.AppOpsManager;
@@ -101,6 +102,7 @@
private String mWritePermission;
private PathPermission[] mPathPermissions;
private boolean mExported;
+ private boolean mNoPerms;
private Transport mTransport = new Transport();
@@ -296,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");
}
@@ -523,10 +525,12 @@
/** @hide */
public final void setAppOps(int readOp, int writeOp) {
- mTransport.mAppOpsManager = (AppOpsManager)mContext.getSystemService(
- Context.APP_OPS_SERVICE);
- mTransport.mReadOp = readOp;
- mTransport.mWriteOp = writeOp;
+ if (!mNoPerms) {
+ mTransport.mAppOpsManager = (AppOpsManager)mContext.getSystemService(
+ Context.APP_OPS_SERVICE);
+ mTransport.mReadOp = readOp;
+ mTransport.mWriteOp = writeOp;
+ }
}
/** @hide */
@@ -1166,6 +1170,15 @@
}
/**
+ * Like {@link #attachInfo(Context, android.content.pm.ProviderInfo)}, but for use
+ * when directly instantiating the provider for testing.
+ * @hide
+ */
+ public void attachInfoForTesting(Context context, ProviderInfo info) {
+ attachInfo(context, info, true);
+ }
+
+ /**
* After being instantiated, this is called to tell the content provider
* about itself.
*
@@ -1173,12 +1186,18 @@
* @param info Registered information about this content provider
*/
public void attachInfo(Context context, ProviderInfo info) {
+ attachInfo(context, info, false);
+ }
+
+ private void attachInfo(Context context, ProviderInfo info, boolean testing) {
/*
* We may be using AsyncTask from binder threads. Make it init here
* so its static handler is on the main thread.
*/
AsyncTask.init();
+ mNoPerms = testing;
+
/*
* Only allow it to be set once, so after the content service gives
* this to us clients can't change it.
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/Debug.java b/core/java/android/os/Debug.java
index e50c948..2e77237 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -45,7 +45,7 @@
/**
- * Provides various debugging functions for Android applications, including
+ * Provides various debugging methods for Android applications, including
* tracing and allocation counts.
* <p><strong>Logging Trace Files</strong></p>
* <p>Debug can create log files that give details about an application, such as
@@ -130,7 +130,7 @@
public int otherSharedDirty;
/** @hide */
- public static final int NUM_OTHER_STATS = 9;
+ public static final int NUM_OTHER_STATS = 10;
private int[] otherStats = new int[NUM_OTHER_STATS*3];
@@ -177,15 +177,16 @@
/* @hide */
public static String getOtherLabel(int which) {
switch (which) {
- case 0: return "Cursor";
- case 1: return "Ashmem";
- case 2: return "Other dev";
- case 3: return ".so mmap";
- case 4: return ".jar mmap";
- case 5: return ".apk mmap";
- case 6: return ".ttf mmap";
- case 7: return ".dex mmap";
- case 8: return "Other mmap";
+ case 0: return "Stack";
+ case 1: return "Cursor";
+ case 2: return "Ashmem";
+ case 3: return "Other dev";
+ case 4: return ".so mmap";
+ case 5: return ".jar mmap";
+ case 6: return ".apk mmap";
+ case 7: return ".ttf mmap";
+ case 8: return ".dex mmap";
+ case 9: return "Other mmap";
default: return "????";
}
}
@@ -554,16 +555,19 @@
/**
* Start counting the number and aggregate size of memory allocations.
*
- * <p>The {@link #startAllocCounting() start} function resets the counts and enables counting.
- * The {@link #stopAllocCounting() stop} function disables the counting so that the analysis
- * code doesn't cause additional allocations. The various <code>get</code> functions return
- * the specified value. And the various <code>reset</code> functions reset the specified
+ * <p>The {@link #startAllocCounting() start} method resets the counts and enables counting.
+ * The {@link #stopAllocCounting() stop} method disables the counting so that the analysis
+ * code doesn't cause additional allocations. The various <code>get</code> methods return
+ * the specified value. And the various <code>reset</code> methods reset the specified
* count.</p>
*
- * <p>Counts are kept for the system as a whole and for each thread.
+ * <p>Counts are kept for the system as a whole (global) and for each thread.
* The per-thread counts for threads other than the current thread
* are not cleared by the "reset" or "start" calls.</p>
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void startAllocCounting() {
VMDebug.startAllocCounting();
}
@@ -577,32 +581,122 @@
VMDebug.stopAllocCounting();
}
+ /**
+ * Returns the global count of objects allocated by the runtime between a
+ * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ */
public static int getGlobalAllocCount() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_OBJECTS);
}
+
+ /**
+ * Clears the global count of objects allocated.
+ * @see #getGlobalAllocCount()
+ */
+ public static void resetGlobalAllocCount() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_OBJECTS);
+ }
+
+ /**
+ * Returns the global size, in bytes, of objects allocated by the runtime between a
+ * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ */
public static int getGlobalAllocSize() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES);
}
+
+ /**
+ * Clears the global size of objects allocated.
+ * @see #getGlobalAllocCountSize()
+ */
+ public static void resetGlobalAllocSize() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES);
+ }
+
+ /**
+ * Returns the global count of objects freed by the runtime between a
+ * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ */
public static int getGlobalFreedCount() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_OBJECTS);
}
+
+ /**
+ * Clears the global count of objects freed.
+ * @see #getGlobalFreedCount()
+ */
+ public static void resetGlobalFreedCount() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_OBJECTS);
+ }
+
+ /**
+ * Returns the global size, in bytes, of objects freed by the runtime between a
+ * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ */
public static int getGlobalFreedSize() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
}
+
+ /**
+ * Clears the global size of objects freed.
+ * @see #getGlobalFreedSize()
+ */
+ public static void resetGlobalFreedSize() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
+ }
+
+ /**
+ * Returns the number of non-concurrent GC invocations between a
+ * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ */
+ public static int getGlobalGcInvocationCount() {
+ return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
+ }
+
+ /**
+ * Clears the count of non-concurrent GC invocations.
+ * @see #getGlobalGcInvocationCount()
+ */
+ public static void resetGlobalGcInvocationCount() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
+ }
+
+ /**
+ * Returns the number of classes successfully initialized (ie those that executed without
+ * throwing an exception) between a {@link #startAllocCounting() start} and
+ * {@link #stopAllocCounting() stop}.
+ */
public static int getGlobalClassInitCount() {
- /* number of classes that have been successfully initialized */
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
}
+
+ /**
+ * Clears the count of classes initialized.
+ * @see #getGlobalClassInitCount()
+ */
+ public static void resetGlobalClassInitCount() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
+ }
+
+ /**
+ * Returns the time spent successfully initializing classes between a
+ * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ */
public static int getGlobalClassInitTime() {
/* cumulative elapsed time for class initialization, in usec */
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
}
/**
- * Returns the global count of external allocation requests. The
- * external allocation tracking feature was removed in Honeycomb.
+ * Clears the count of time spent initializing classes.
+ * @see #getGlobalClassInitTime()
+ */
+ public static void resetGlobalClassInitTime() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
+ }
+
+ /**
* This method exists for compatibility and always returns 0.
- *
* @deprecated This method is now obsolete.
*/
@Deprecated
@@ -611,10 +705,21 @@
}
/**
- * Returns the global count of bytes externally allocated. The
- * external allocation tracking feature was removed in Honeycomb.
+ * This method exists for compatibility and has no effect.
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetGlobalExternalAllocSize() {}
+
+ /**
+ * This method exists for compatibility and has no effect.
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetGlobalExternalAllocCount() {}
+
+ /**
* This method exists for compatibility and always returns 0.
- *
* @deprecated This method is now obsolete.
*/
@Deprecated
@@ -623,11 +728,7 @@
}
/**
- * Returns the global count of freed external allocation requests.
- * The external allocation tracking feature was removed in
- * Honeycomb. This method exists for compatibility and always
- * returns 0.
- *
+ * This method exists for compatibility and always returns 0.
* @deprecated This method is now obsolete.
*/
@Deprecated
@@ -636,11 +737,14 @@
}
/**
- * Returns the global count of freed bytes from external
- * allocation requests. The external allocation tracking feature
- * was removed in Honeycomb. This method exists for compatibility
- * and always returns 0.
- *
+ * This method exists for compatibility and has no effect.
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetGlobalExternalFreedCount() {}
+
+ /**
+ * This method exists for compatibility and has no effect.
* @deprecated This method is now obsolete.
*/
@Deprecated
@@ -648,22 +752,48 @@
return 0;
}
- public static int getGlobalGcInvocationCount() {
- return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
- }
+ /**
+ * This method exists for compatibility and has no effect.
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetGlobalExternalFreedSize() {}
+
+ /**
+ * Returns the thread-local count of objects allocated by the runtime between a
+ * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ */
public static int getThreadAllocCount() {
return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_OBJECTS);
}
+
+ /**
+ * Clears the thread-local count of objects allocated.
+ * @see #getThreadAllocCount()
+ */
+ public static void resetThreadAllocCount() {
+ VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_OBJECTS);
+ }
+
+ /**
+ * Returns the thread-local size of objects allocated by the runtime between a
+ * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ * @return The allocated size in bytes.
+ */
public static int getThreadAllocSize() {
return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
}
/**
- * Returns the count of external allocation requests made by the
- * current thread. The external allocation tracking feature was
- * removed in Honeycomb. This method exists for compatibility and
- * always returns 0.
- *
+ * Clears the thread-local count of objects allocated.
+ * @see #getThreadAllocSize()
+ */
+ public static void resetThreadAllocSize() {
+ VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
+ }
+
+ /**
+ * This method exists for compatibility and has no effect.
* @deprecated This method is now obsolete.
*/
@Deprecated
@@ -672,10 +802,14 @@
}
/**
- * Returns the global count of bytes externally allocated. The
- * external allocation tracking feature was removed in Honeycomb.
- * This method exists for compatibility and always returns 0.
- *
+ * This method exists for compatibility and has no effect.
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public static void resetThreadExternalAllocCount() {}
+
+ /**
+ * This method exists for compatibility and has no effect.
* @deprecated This method is now obsolete.
*/
@Deprecated
@@ -683,105 +817,33 @@
return 0;
}
- public static int getThreadGcInvocationCount() {
- return VMDebug.getAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
- }
-
- public static void resetGlobalAllocCount() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_OBJECTS);
- }
- public static void resetGlobalAllocSize() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES);
- }
- public static void resetGlobalFreedCount() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_OBJECTS);
- }
- public static void resetGlobalFreedSize() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
- }
- public static void resetGlobalClassInitCount() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
- }
- public static void resetGlobalClassInitTime() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
- }
-
/**
- * Resets the global count of external allocation requests. The
- * external allocation tracking feature was removed in Honeycomb.
* This method exists for compatibility and has no effect.
- *
- * @deprecated This method is now obsolete.
- */
- @Deprecated
- public static void resetGlobalExternalAllocCount() {}
-
- /**
- * Resets the global count of bytes externally allocated. The
- * external allocation tracking feature was removed in Honeycomb.
- * This method exists for compatibility and has no effect.
- *
- * @deprecated This method is now obsolete.
- */
- @Deprecated
- public static void resetGlobalExternalAllocSize() {}
-
- /**
- * Resets the global count of freed external allocations. The
- * external allocation tracking feature was removed in Honeycomb.
- * This method exists for compatibility and has no effect.
- *
- * @deprecated This method is now obsolete.
- */
- @Deprecated
- public static void resetGlobalExternalFreedCount() {}
-
- /**
- * Resets the global count counter of freed bytes from external
- * allocations. The external allocation tracking feature was
- * removed in Honeycomb. This method exists for compatibility and
- * has no effect.
- *
- * @deprecated This method is now obsolete.
- */
- @Deprecated
- public static void resetGlobalExternalFreedSize() {}
-
- public static void resetGlobalGcInvocationCount() {
- VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
- }
- public static void resetThreadAllocCount() {
- VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_OBJECTS);
- }
- public static void resetThreadAllocSize() {
- VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
- }
-
- /**
- * Resets the count of external allocation requests made by the
- * current thread. The external allocation tracking feature was
- * removed in Honeycomb. This method exists for compatibility and
- * has no effect.
- *
- * @deprecated This method is now obsolete.
- */
- @Deprecated
- public static void resetThreadExternalAllocCount() {}
-
- /**
- * Resets the count of bytes externally allocated by the current
- * thread. The external allocation tracking feature was removed
- * in Honeycomb. This method exists for compatibility and has no
- * effect.
- *
* @deprecated This method is now obsolete.
*/
@Deprecated
public static void resetThreadExternalAllocSize() {}
+ /**
+ * Returns the number of thread-local non-concurrent GC invocations between a
+ * {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ */
+ public static int getThreadGcInvocationCount() {
+ return VMDebug.getAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
+ }
+
+ /**
+ * Clears the thread-local count of non-concurrent GC invocations.
+ * @see #getThreadGcInvocationCount()
+ */
public static void resetThreadGcInvocationCount() {
VMDebug.resetAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
}
+
+ /**
+ * Clears all the global and thread-local memory allocation counters.
+ * @see #startAllocCounting()
+ */
public static void resetAllCounts() {
VMDebug.resetAllocCount(VMDebug.KIND_ALL_COUNTS);
}
@@ -1380,7 +1442,7 @@
}
/**
- * @return a String describing the immediate caller of the calling function.
+ * @return a String describing the immediate caller of the calling method.
* {@hide}
*/
public static String getCaller() {
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 1bada67..d9846ec 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.GuardedBy;
import java.io.File;
+import java.io.IOException;
/**
* Provides access to environment variables.
@@ -36,12 +37,16 @@
private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
+ private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
/** {@hide} */
public static String DIRECTORY_ANDROID = "Android";
- private static final File ROOT_DIRECTORY
- = getDirectory("ANDROID_ROOT", "/system");
+ private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
+ private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");
+
+ private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull(
+ ENV_EMULATED_STORAGE_TARGET);
private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
@@ -178,7 +183,7 @@
* Gets the Android root directory.
*/
public static File getRootDirectory() {
- return ROOT_DIRECTORY;
+ return DIR_ANDROID_ROOT;
}
/**
@@ -632,6 +637,19 @@
return path == null ? new File(defaultPath) : new File(path);
}
+ private static String getCanonicalPathOrNull(String variableName) {
+ String path = System.getenv(variableName);
+ if (path == null) {
+ return null;
+ }
+ try {
+ return new File(path).getCanonicalPath();
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to resolve canonical path for " + path);
+ return null;
+ }
+ }
+
private static void throwIfSystem() {
if (Process.myUid() == Process.SYSTEM_UID) {
Log.wtf(TAG, "Static storage paths aren't available from AID_SYSTEM", new Throwable());
@@ -649,4 +667,40 @@
}
return cur;
}
+
+ /**
+ * If the given path exists on emulated external storage, return the
+ * translated backing path hosted on internal storage. This bypasses any
+ * emulation later, improving performance. This is <em>only</em> suitable
+ * for read-only access.
+ * <p>
+ * Returns original path if given path doesn't meet these criteria. Callers
+ * must hold {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}
+ * permission.
+ *
+ * @hide
+ */
+ public static File maybeTranslateEmulatedPathToInternal(File path) {
+ // Fast return if not emulated, or missing variables
+ if (!Environment.isExternalStorageEmulated()
+ || CANONCIAL_EMULATED_STORAGE_TARGET == null) {
+ return path;
+ }
+
+ try {
+ final String rawPath = path.getCanonicalPath();
+ if (rawPath.startsWith(CANONCIAL_EMULATED_STORAGE_TARGET)) {
+ final File internalPath = new File(DIR_MEDIA_STORAGE,
+ rawPath.substring(CANONCIAL_EMULATED_STORAGE_TARGET.length()));
+ if (internalPath.exists()) {
+ return internalPath;
+ }
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to resolve canonical path for " + path);
+ }
+
+ // Unable to translate to internal path; use original
+ return path;
+ }
}
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/provider/Settings.java b/core/java/android/provider/Settings.java
index 8715349..266d0d3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -511,6 +511,9 @@
* extra to the Intent with one or more syncable content provider's authorities. Only account
* types which can sync with that content provider will be offered to the user.
* <p>
+ * Account types can also be filtered by adding an {@link #EXTRA_ACCOUNT_TYPES} extra to the
+ * Intent with one or more account types.
+ * <p>
* Input: Nothing.
* <p>
* Output: Nothing.
@@ -693,8 +696,9 @@
* Example: The {@link #ACTION_ADD_ACCOUNT} intent restricts the account types available based
* on the authority given.
*/
- public static final String EXTRA_AUTHORITIES =
- "authorities";
+ public static final String EXTRA_AUTHORITIES = "authorities";
+
+ public static final String EXTRA_ACCOUNT_TYPES = "account_types";
public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index c5261f3..98316ae 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -180,6 +180,7 @@
if (composed != 0) {
i = composed;
replace = true;
+ dead = false;
}
}
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 2cb724f..9e5f25a 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -180,6 +180,8 @@
private static final int ACCENT_CIRCUMFLEX_LEGACY = '^';
private static final int ACCENT_TILDE_LEGACY = '~';
+ private static final int CHAR_SPACE = ' ';
+
/**
* Maps Unicode combining diacritical to display-form dead key.
*/
@@ -473,14 +475,23 @@
}
/**
- * Get the character that is produced by putting accent on the character c.
+ * Get the character that is produced by combining the dead key producing accent
+ * with the key producing character c.
* For example, getDeadChar('`', 'e') returns è.
+ * getDeadChar('^', ' ') returns '^' and getDeadChar('^', '^') returns '^'.
*
* @param accent The accent character. eg. '`'
* @param c The basic character.
* @return The combined character, or 0 if the characters cannot be combined.
*/
public static int getDeadChar(int accent, int c) {
+ if (c == accent || CHAR_SPACE == c) {
+ // The same dead character typed twice or a dead character followed by a
+ // space should both produce the non-combining version of the combining char.
+ // In this case we don't even need to compute the combining character.
+ return accent;
+ }
+
int combining = sAccentToCombining.get(accent);
if (combining == 0) {
return 0;
@@ -495,7 +506,8 @@
sDeadKeyBuilder.append((char)c);
sDeadKeyBuilder.append((char)combining);
String result = Normalizer.normalize(sDeadKeyBuilder, Normalizer.Form.NFC);
- combined = result.length() == 1 ? result.charAt(0) : 0;
+ combined = result.codePointCount(0, result.length()) == 1
+ ? result.codePointAt(0) : 0;
sDeadKeyCache.put(combination, combined);
}
}
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/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index c3ef54c..fe3e5c6 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -50,7 +50,7 @@
void removeAccessibilityInteractionConnection(IWindow windowToken);
- void registerUiTestAutomationService(IAccessibilityServiceClient client,
+ void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,
in AccessibilityServiceInfo info);
void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
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 728bcd3..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();
}
@@ -1103,6 +1114,11 @@
* false. See also {@link #setDatabasePath} for how to correctly set up the
* database storage API.
*
+ * This setting is global in effect, across all WebView instances in a process.
+ * Note you should only modify this setting prior to making <b>any</b> WebView
+ * page load within a given process, as the WebView implementation may ignore
+ * changes to this setting after that point.
+ *
* @param flag true if the WebView should use the database storage API
*/
public synchronized void setDatabaseEnabled(boolean flag) {
@@ -1219,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();
}
@@ -1324,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 1321515..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>
*
@@ -334,8 +310,9 @@
* See {@link WebView#capturePicture} for details of the picture.
*
* @param view the WebView that owns the picture
- * @param picture the new picture. Applications targetting Jelly
- * Bean MR2 or above will always receive a null Picture.
+ * @param picture the new picture. Applications targeting
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
+ * will always receive a null Picture.
* @deprecated Deprecated due to internal changes.
*/
@Deprecated
@@ -601,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);
@@ -998,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();
@@ -1388,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/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 19784a4..6fefcca 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -7909,10 +7909,8 @@
if (mPictureListener != null) {
// trigger picture listener for hardware layers. Software layers are
// triggered in setNewPicture
- // TODO: Update CUR_DEVELOPMENT when appropriate JBMR2 constant is
- // available.
Picture picture = mContext.getApplicationInfo().targetSdkVersion <
- Build.VERSION_CODES.CUR_DEVELOPMENT ? capturePicture() : null;
+ Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
mPictureListener.onNewPicture(getWebView(), picture);
}
}
@@ -7998,10 +7996,8 @@
|| mWebView.getLayerType() == View.LAYER_TYPE_SOFTWARE) {
// trigger picture listener for software layers. Hardware layers are
// triggered in pageSwapCallback
- // TODO: Update CUR_DEVELOPMENT when appropriate JBMR2 constant is
- // available.
Picture picture = mContext.getApplicationInfo().targetSdkVersion <
- Build.VERSION_CODES.CUR_DEVELOPMENT ? capturePicture() : null;
+ Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
mPictureListener.onNewPicture(getWebView(), picture);
}
}
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/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java
index 0c6b780..cf69a9d 100644
--- a/core/java/com/android/internal/view/ActionBarPolicy.java
+++ b/core/java/com/android/internal/view/ActionBarPolicy.java
@@ -19,6 +19,7 @@
import com.android.internal.R;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
@@ -44,7 +45,10 @@
}
public boolean showsOverflowMenuButton() {
- return !ViewConfiguration.get(mContext).hasPermanentMenuKey();
+ return !ViewConfiguration.get(mContext).hasPermanentMenuKey() ||
+ ((mContext.getResources().getConfiguration().uiMode &
+ Configuration.UI_MODE_TYPE_TELEVISION) ==
+ Configuration.UI_MODE_TYPE_TELEVISION);
}
public int getEmbeddedMenuWidthLimit() {
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_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 6e21a11..2883c10 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -43,6 +43,7 @@
HEAP_UNKNOWN,
HEAP_DALVIK,
HEAP_NATIVE,
+ HEAP_STACK,
HEAP_CURSOR,
HEAP_ASHMEM,
HEAP_UNKNOWN_DEV,
@@ -109,7 +110,7 @@
static jlong android_os_Debug_getNativeHeapFreeSize(JNIEnv *env, jobject clazz)
{
-#ifdef HAVE_MALLOC_H
+#ifdef HAVE_MALLOC_H
struct mallinfo info = mallinfo();
return (jlong) info.fordblks;
#else
@@ -164,6 +165,8 @@
whichHeap = HEAP_NATIVE;
} else if (strstr(name, "/dev/ashmem/dalvik-") == name) {
whichHeap = HEAP_DALVIK;
+ } else if (strstr(name, "[stack") == name) {
+ whichHeap = HEAP_STACK;
} else if (strstr(name, "/dev/ashmem/CursorWindow") == name) {
whichHeap = HEAP_CURSOR;
} else if (strstr(name, "/dev/ashmem/") == name) {
@@ -191,7 +194,7 @@
//ALOGI("native=%d dalvik=%d sqlite=%d: %s\n", isNativeHeap, isDalvikHeap,
// isSqliteHeap, line);
-
+
while (true) {
if (fgets(line, 1024, fp) == 0) {
done = true;
@@ -233,7 +236,7 @@
{
char tmp[128];
FILE *fp;
-
+
sprintf(tmp, "/proc/%d/smaps", pid);
fp = fopen(tmp, "r");
if (fp == 0) return;
@@ -247,7 +250,7 @@
{
stats_t stats[_NUM_HEAP];
memset(&stats, 0, sizeof(stats));
-
+
load_maps(pid, stats);
for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) {
@@ -261,9 +264,9 @@
env->SetIntField(object, stat_fields[i].privateDirty_field, stats[i].privateDirty);
env->SetIntField(object, stat_fields[i].sharedDirty_field, stats[i].sharedDirty);
}
-
+
jintArray otherIntArray = (jintArray)env->GetObjectField(object, otherStats_field);
-
+
jint* otherArray = (jint*)env->GetPrimitiveArrayCritical(otherIntArray, 0);
if (otherArray == NULL) {
return;
@@ -328,7 +331,7 @@
char compare[128];
int len = snprintf(compare, 128, "proc %d", getpid());
-
+
// loop until we have the block that represents this process
do {
if (fgets(line, 1024, fp) == 0) {
@@ -336,15 +339,15 @@
}
} while (strncmp(compare, line, len));
- // now that we have this process, read until we find the stat that we are looking for
+ // now that we have this process, read until we find the stat that we are looking for
len = snprintf(compare, 128, " %s: ", stat);
-
+
do {
if (fgets(line, 1024, fp) == 0) {
return -1;
}
} while (strncmp(compare, line, len));
-
+
// we have the line, now increment the line ptr to the value
char* ptr = line + len;
return atoi(ptr);
@@ -510,7 +513,7 @@
jobject fileDescriptor)
{
if (fileDescriptor == NULL) {
- jniThrowNullPointerException(env, NULL);
+ jniThrowNullPointerException(env, "fd == null");
return;
}
int origFd = jniGetFDFromFileDescriptor(env, fileDescriptor);
@@ -547,7 +550,7 @@
jint pid, jstring fileName)
{
if (fileName == NULL) {
- jniThrowNullPointerException(env, NULL);
+ jniThrowNullPointerException(env, "file == null");
return;
}
const jchar* str = env->GetStringCritical(fileName, 0);
@@ -611,6 +614,19 @@
{
jclass clazz = env->FindClass("android/os/Debug$MemoryInfo");
+ // Sanity check the number of other statistics expected in Java matches here.
+ jfieldID numOtherStats_field = env->GetStaticFieldID(clazz, "NUM_OTHER_STATS", "I");
+ jint numOtherStats = env->GetStaticIntField(clazz, numOtherStats_field);
+ int expectedNumOtherStats = _NUM_HEAP - _NUM_CORE_HEAP;
+ if (numOtherStats != expectedNumOtherStats) {
+ jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+ "android.os.Debug.Meminfo.NUM_OTHER_STATS=%d expected %d",
+ numOtherStats, expectedNumOtherStats);
+ return JNI_ERR;
+ }
+
+ otherStats_field = env->GetFieldID(clazz, "otherStats", "[I");
+
for (int i=0; i<_NUM_CORE_HEAP; i++) {
stat_fields[i].pss_field =
env->GetFieldID(clazz, stat_field_names[i].pss_name, "I");
@@ -620,8 +636,6 @@
env->GetFieldID(clazz, stat_field_names[i].sharedDirty_name, "I");
}
- otherStats_field = env->GetFieldID(clazz, "otherStats", "[I");
-
return jniRegisterNativeMethods(env, "android/os/Debug", gMethods, NELEM(gMethods));
}
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..47264b0 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/distribute/googleplay/about/distribution.jd b/docs/html/distribute/googleplay/about/distribution.jd
index 1ddc4ca..7d90426 100644
--- a/docs/html/distribute/googleplay/about/distribution.jd
+++ b/docs/html/distribute/googleplay/about/distribution.jd
@@ -75,24 +75,29 @@
Google Play Developer Console, you can list the supported devices and
even exclude specific devices if needed.</p>
-<h2 id="stats">Statistics for analyzing installs</h2>
+<h2 id="stats">Statistics for analyzing installs and ratings</h2>
<p>Once you’ve published your app, Google Play makes it easy to see how it’s
doing. The Google Play Developer Console gives you access to a variety
-of anonymized metrics that show your app’s installation performance measured by
-unique users and unique devices, across a variety of different dimensions such
-as country, Android version, device, country, carrier, and app version.</p>
+of anonymized statistics and custom charts that show you the app's installation
+performance and ratings.</p>
-<div style="border:1px solid #DDD;margin:1.5em;margin-left:8%;width:608px">
-<img src="{@docRoot}images/gp-dc-stats-mini.png"
-style="width:600px;padding:4px;margin-bottom:0;">
+<p>You can view data and charts for active, daily, and total installs
+per unique devices or users, as well as upgrades and uninstalls.
+You can also view the app's daily average user rating and its cumulative
+user rating. To help you analyze the data, you can view install
+and ratings statistics across a variety of different dimensions such as Android
+version, device, country, app version, and carrier.</p>
+
+<div class="figure-left">
+ <img src="{@docRoot}images/gp-dc-stats-mini.png" class="frame">
</div>
-<p>You can also view your installation data on timeline charts, for all metrics and
-dimensions. At a glance, these charts highlight your app’s installation peaks
-and longer-term trends, which you can correlate to promotions, app improvements,
-or other factors. You can even focus in on data inside a dimension by
-highlighting specific data points (such as individual platform versions or
-languages) on the timeline.</p>
+<p>You can see your app statistics on timeline charts, for
+all metrics and dimensions. At a glance, the charts highlight your app’s
+installation and ratings peaks and longer-term trends, which you can correlate
+to promotions, app improvements, or other factors. You can even focus in on
+data inside a dimension by highlighting specific data points (such as
+individual platform versions or languages) on the timeline.</p>
<p>So that you can “take your data with you”, you can download all of your
installation data as a CSV file for viewing in the business program of your
@@ -123,7 +128,7 @@
upload the expansion files, Google Play hosts them for free and handles the
download of the files as part of the normal APK installation.</p>
-<h2 id="licensing">Protecting your App</h2>
+<h2 id="licensing">Protecting your app</h2>
<p>Google Play provides two key features to help you protect your application
against piracy — Google Play Licensing and app encryption.</p>
diff --git a/docs/html/distribute/googleplay/publish/console.jd b/docs/html/distribute/googleplay/publish/console.jd
index 069b2d2..0bf5e78 100644
--- a/docs/html/distribute/googleplay/publish/console.jd
+++ b/docs/html/distribute/googleplay/publish/console.jd
@@ -4,7 +4,7 @@
<p>Once you've <a
href="{@docRoot}distribute/googleplay/publish/register.html">registered</a> and
-received verification by email, you can sign in to your Google Play Android
+received verification by email, you can sign in to your Google Play
Developer Console, which will be the home for your app publishing operations and
tools on Google Play. This sections below introduce a few of the key areas
you'll find in the Developer Console.</p>
diff --git a/docs/html/distribute/googleplay/publish/preparing.jd b/docs/html/distribute/googleplay/publish/preparing.jd
index 0925f3c..416f02e 100644
--- a/docs/html/distribute/googleplay/publish/preparing.jd
+++ b/docs/html/distribute/googleplay/publish/preparing.jd
@@ -203,11 +203,19 @@
changes in your app binary, so you will need to make those changes before
creating your release-ready APK.</p>
+<p>To minimize the size of your app binary, make sure that you run the
+<a href="{@docRoot}tools/help/proguard.html">Proguard</a> tool on your code when
+building your release-ready APK.</p>
+
<table>
<tr>
<td><p>Related resources:</p>
<ul style="margin-top:-.5em;">
-<li><strong><a href="{@docRoot}google/play/expansion-files.html">APK Expansion Files</a></strong> — Developer documentation describing APK Expansion Files and how to support them in your app.</li>
+<li><strong><a href="{@docRoot}google/play/expansion-files.html">APK Expansion Files</a></strong>
+— Developer documentation describing APK Expansion Files and how to support them in your app.</li>
+<li><strong><a href="{@docRoot}tools/help/proguard.html">ProGuard</a></strong> — Developer
+documentation describing how to use ProGuard to shrink, optimize, and obfuscate your code prior
+to release.</li>
</ul>
</td>
</tr>
diff --git a/docs/html/google/play/filters.jd b/docs/html/google/play/filters.jd
index a68f4910..6ab223c 100644
--- a/docs/html/google/play/filters.jd
+++ b/docs/html/google/play/filters.jd
@@ -394,9 +394,10 @@
determine the country based on IP.</p></li> <li><p>Carrier is determined based on
the device's SIM (for GSM devices), not the current roaming carrier.</p></li></ul>
</td> </tr> <tr>
- <td valign="top">Native Platform</td> <td valign="top"><p>An application that includes native
- libraries that target a specific platform (ARM EABI v7 or x86, for example) are
- visible only on devices that support that platform. For details about the NDK and using
+ <td valign="top" style="white-space:nowrap;">CPU Architecture (ABI)</td>
+ <td valign="top"><p>An application that includes native
+ libraries that target a specific CPU architecture (ARM EABI v7 or x86, for example) are
+ visible only on devices that support that architecture. For details about the NDK and using
native libraries, see <a href="{@docRoot}tools/sdk/ndk/index.html#overview">What is the
Android NDK?</a></p> </tr> <tr>
<td valign="top">Copy-Protected Applications</td> <td valign="top"><p class="caution">Google
@@ -433,9 +434,13 @@
<p>By using the <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html">{@code
<uses-sdk>}</a> element.</p>
</li>
+ <li>CPU Architecture (ABI)
+ <p>By including native libraries built with the <a href="{@docRoot}tools/sdk/ndk/index.html">Android
+ NDK</a> that target a specific CPU architecture (ARM EABI v7 or x86, for example).</p>
+ </li>
</ul>
-<p>All other filters still work the same as usual, but these three are the only filters that can
+<p>All other filters still work the same as usual, but these four are the only filters that can
distinguish one APK from another within the same application listing on Google Play. For example,
you <em>cannot</em> publish multiple APKs for the same application if the APKs differ only based on
whether the device has a camera.</p>
diff --git a/docs/html/guide/topics/graphics/hardware-accel.jd b/docs/html/guide/topics/graphics/hardware-accel.jd
index 04fb564..01dd79a 100644
--- a/docs/html/guide/topics/graphics/hardware-accel.jd
+++ b/docs/html/guide/topics/graphics/hardware-accel.jd
@@ -270,110 +270,250 @@
android.graphics.Canvas} drawing operations as well as many less-used operations. All of the
drawing operations that are used to render applications that ship with Android, default widgets
and layouts, and common advanced visual effects such as reflections and tiled textures are
- supported. The following list describes known operations that are <strong>not supported</strong>
- with hardware acceleration:</p>
+ supported.</p>
- <ul>
- <li>
- <strong>Canvas</strong>
+ <p>The following table describes the support level of various operations across API levels:</p>
- <ul>
- <li>{@link android.graphics.Canvas#clipPath clipPath()}</li>
-
- <li>{@link android.graphics.Canvas#clipRegion clipRegion()}</li>
-
- <li>{@link android.graphics.Canvas#drawPicture drawPicture()}</li>
-
- <li>{@link android.graphics.Canvas#drawTextOnPath drawTextOnPath()}</li>
-
- <li>{@link android.graphics.Canvas#drawVertices drawVertices()}</li>
- </ul>
- </li>
-
- <li>
- <strong>Paint</strong>
-
- <ul>
- <li>{@link android.graphics.Paint#setLinearText setLinearText()}</li>
-
- <li>{@link android.graphics.Paint#setMaskFilter setMaskFilter()}</li>
-
- <li>{@link android.graphics.Paint#setRasterizer setRasterizer()}</li>
- </ul>
- </li>
-
- <li>
- <strong>Xfermodes</strong>
-
- <ul>
- <li>{@link android.graphics.AvoidXfermode AvoidXfermode}</li>
-
- <li>{@link android.graphics.PixelXorXfermode PixelXorXfermode}</li>
- </ul>
- </li>
- </ul>
-
- <p>In addition, some operations behave differently with hardware acceleration enabled:</p>
-
- <ul>
- <li>
- <strong>Canvas</strong>
-
- <ul>
- <li>{@link android.graphics.Canvas#clipRect clipRect()}: <code>XOR</code>,
- <code>Difference</code> and <code>ReverseDifference</code> clip modes are ignored. 3D
- transforms do not apply to the clip rectangle</li>
-
- <li>{@link android.graphics.Canvas#drawBitmapMesh drawBitmapMesh()}: colors array is
- ignored</li>
- </ul>
- </li>
-
- <li>
- <strong>Paint</strong>
-
- <ul>
- <li>{@link android.graphics.Paint#setDither setDither()}: ignored</li>
-
- <li>{@link android.graphics.Paint#setFilterBitmap setFilterBitmap()}: filtering is always
- on</li>
-
- <li>{@link android.graphics.Paint#setShadowLayer setShadowLayer()}: works with text
- only</li>
- </ul>
- </li>
-
- <li>
- <strong>PorterDuffXfermode</strong>
-
- <ul>
- <li>{@link android.graphics.PorterDuff.Mode#DARKEN PorterDuff.Mode.DARKEN} will
- be equivalent to {@link android.graphics.PorterDuff.Mode#SRC_OVER} when blending
- against the framebuffer.</li>
-
- <li>{@link android.graphics.PorterDuff.Mode#LIGHTEN PorterDuff.Mode.LIGHTEN} will
- be equivalent to {@link android.graphics.PorterDuff.Mode#SRC_OVER} when blending
- against the framebuffer.</li>
-
- <li>{@link android.graphics.PorterDuff.Mode#OVERLAY PorterDuff.Mode.OVERLAY} will
- be equivalent to {@link android.graphics.PorterDuff.Mode#SRC_OVER} when blending
- against the framebuffer.</li>
- </ul>
- </li>
-
- <li>
- <strong>ComposeShader</strong>
-
- <ul>
- <li>{@link android.graphics.ComposeShader} can only contain shaders of different types (a
- {@link android.graphics.BitmapShader} and a {@link android.graphics.LinearGradient} for
- instance, but not two instances of {@link android.graphics.BitmapShader} )</li>
-
- <li>{@link android.graphics.ComposeShader} cannot contain a {@link
- android.graphics.ComposeShader}</li>
- </ul>
- </li>
- </ul>
+ <style type="text/css">
+ .tblGenFixed,.tblGeneric{font-size:15px}.tblGenFixed td {padding:0 3px;letter-spacing:0;word-spacing:0;background-color:#fff;z-index:1;border-top:0px none;border-left:0px none;border-bottom:1px solid #CCC;border-right:1px solid #CCC;} .dn {display:none} .tblGenFixed td.s0 {background-color:white;border-top:1px solid #CCC;border-left:1px solid #CCC;} .tblGenFixed td.s2 {background-color:#d9d9d9;color:#000000;text-align:center;} .tblGenFixed td.s1 {background-color:#434343;color:#ffffff;text-align:center;border-top:1px solid #CCC;} .tblGenFixed td.s9 {background-color:;color:#6aa84f;text-align:center;} .tblGenFixed td.s12 {background-color:white;color:#6aa84f;text-align:center;} .tblGenFixed td.s13 {background-color:#d9d9d9;color:#6aa84f;text-align:center;} .tblGenFixed td.s7 {background-color:#d9d9d9;color:#980000;text-align:center;} .tblGenFixed td.s8 {background-color:;color:#980000;text-align:center;} .tblGenFixed td.s5 {background-color:#434343;color:#ffffff;text-align:left;border-left:1px solid #CCC;} .tblGenFixed td.s6 {background-color:;font-family:courier new,monospace;color:;text-align:right;border-left:1px solid #CCC;} .tblGenFixed td.s10 {background-color:white;font-family:courier new,monospace;color:#000000;text-align:right;border-left:1px solid #CCC;} .tblGenFixed td.s3 {background-color:white;color:#000000;text-align:center;} .tblGenFixed td.s11 {background-color:white;color:#980000;text-align:center;} .tblGenFixed td.s4 {background-color:#d9d9d9;color:#000000;text-align:center;}
+ </style>
+ <table border="0" cellpadding="0" cellspacing="0" class="tblGenFixed" id="tblMain">
+ <tbody>
+ <tr class="rShim">
+ <td class="rShim" style="width:380px;"></td>
+ <td class="rShim" style="width:120px;"></td>
+ <td class="rShim" style="width:120px;"></td>
+ <td class="rShim" style="width:120px;"></td>
+ <td class="rShim" style="width:120px;"></td>
+ </tr>
+ <tr>
+ <td rowspan="2" class="s0"></td>
+ <td colspan="4" class="s1">API level</td>
+ </tr>
+ <tr>
+ <td style="display:none;"></td>
+ <td class="s2">< 16</td>
+ <td class="s3">16</td>
+ <td class="s4">17</td>
+ <td class="s3">18</td>
+ </tr>
+ <tr>
+ <td colspan="5" class="s5">Canvas</td>
+ </tr>
+ <tr>
+ <td class="s6">clipPath()</td>
+ <td class="s7">✗</td>
+ <td class="s8">✗</td>
+ <td class="s7">✗</td>
+ <td class="s9">✓</td>
+ </tr>
+ <tr>
+ <td class="s10">clipRegion()</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s10">clipRect(Region.Op.XOR)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s10">clipRect(Region.Op.Difference)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s10">clipRect(Region.Op.ReverseDifference)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s10">drawBitmapMesh() (colors array)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s10">drawPicture()</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">drawPosText()</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ <td class="s13">✓</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s10">drawTextOnPath()</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ <td class="s13">✓</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s10">drawVertices()</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">setDrawFilter()</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ <td class="s13">✓</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td colspan="5" class="s5">Paint</td>
+ </tr>
+ <tr>
+ <td class="s6">setAntiAlias() (for text)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s6">setAntiAlias() (for lines)</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ <td class="s13">✓</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s6">setFilterBitmap()</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s13">✓</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s6">setLinearText()</td>
+ <td class="s7">✗</td>
+ <td class="s8">✗</td>
+ <td class="s7">✗</td>
+ <td class="s8">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">setMaskFilter()</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">setPathEffect() (for lines)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">setRasterizer()</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">setShadowLayer() (other than text)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">setStrokeCap() (for lines)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s12">✓</td>
+ </tr>
+ <tr>
+ <td class="s10">setStrokeCap() (for points)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">setSubpixelText()</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td colspan="5" class="s5">Xfermode</td>
+ </tr>
+ <tr>
+ <td class="s6">AvoidXfermode</td>
+ <td class="s7">✗</td>
+ <td class="s8">✗</td>
+ <td class="s7">✗</td>
+ <td class="s8">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">PixelXorXfermode</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">PorterDuff.Mode.DARKEN (framebuffer)</td>
+ <td class="s7">✗</td>
+ <td class="s8">✗</td>
+ <td class="s7">✗</td>
+ <td class="s8">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">PorterDuff.Mode.LIGHTEN (framebuffer)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">PorterDuff.Mode.OVERLAY (framebuffer)</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td colspan="5" class="s5">Shader</td>
+ </tr>
+ <tr>
+ <td class="s10">ComposeShader inside ComposeShader</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ <tr>
+ <td class="s10">Same type shaders inside ComposeShader</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ <td class="s7">✗</td>
+ <td class="s11">✗</td>
+ </tr>
+ </tbody>
+ </table>
<p>If your application is affected by any of these missing features or limitations, you can turn
off hardware acceleration for just the affected portion of your application by calling
diff --git a/docs/html/reference/com/google/android/gms/common/Scopes.html b/docs/html/reference/com/google/android/gms/common/Scopes.html
index 847a613..07afe05 100644
--- a/docs/html/reference/com/google/android/gms/common/Scopes.html
+++ b/docs/html/reference/com/google/android/gms/common/Scopes.html
@@ -982,44 +982,6 @@
-
-<A NAME="GAMES"></A>
-
-<div class="jd-details api apilevel-">
- <h4 class="jd-details-title">
- <span class="normal">
- public
- static
- final
- String
- </span>
- GAMES
- </h4>
- <div class="api-level">
-
-
-
-
- </div>
- <div class="jd-details-descr">
-
- <div class="jd-tagdata jd-tagdescr"><p>Scope for accessing data from the games service. </p></div>
-
-
- <div class="jd-tagdata">
- <span class="jd-tagtitle">Constant Value: </span>
- <span>
-
- "https://www.googleapis.com/auth/games"
-
- </span>
- </div>
-
- </div>
-</div>
-
-
-
<A NAME="PLUS_LOGIN"></A>
<div class="jd-details api apilevel-">
diff --git a/docs/html/tools/device.jd b/docs/html/tools/device.jd
index cf7b63f..9bdaf47 100644
--- a/docs/html/tools/device.jd
+++ b/docs/html/tools/device.jd
@@ -213,6 +213,10 @@
<td><code>22b8</code></td>
</tr>
<tr>
+ <td>MTK</td>
+ <td><code>0e8d</code></td>
+ </tr>
+ <tr>
<td>NEC</td>
<td><code>0409</code></td>
</tr>
diff --git a/docs/html/tools/extras/oem-usb.jd b/docs/html/tools/extras/oem-usb.jd
index 79b242b..774fe87 100644
--- a/docs/html/tools/extras/oem-usb.jd
+++ b/docs/html/tools/extras/oem-usb.jd
@@ -299,9 +299,14 @@
</tr>
<tr><td>LGE</td> <td><a
href="http://www.lg.com/us/mobile-phones/mobile-support/mobile-lg-mobile-phone-support.jsp">http://www.lg.com/us/mobile-phones/mobile-support/mobile-lg-mobile-phone-support.jsp</a></td>
-</tr><tr><td>Motorola</td> <td><a
+</tr>
+<tr><td>Motorola</td> <td><a
href="http://developer.motorola.com/docstools/USB_Drivers/">http://developer.motorola.com/docstools/USB_Drivers/</a></td>
-</tr><tr><td>Pantech</td> <td><a
+</tr>
+<tr><td>MTK</td> <td><a
+href="http://online.mediatek.com/Public%20Documents/MTK_Android_USB_Driver.zip">http://online.mediatek.com/Public%20Documents/MTK_Android_USB_Driver.zip</a></td>
+</tr>
+<tr><td>Pantech</td> <td><a
href="http://www.isky.co.kr/cs/software/software.sky?fromUrl=index">http://www.isky.co.kr/cs/software/software.sky?fromUrl=index</a></td>
</tr><tr><td>Pegatron</td> <td><a
href="http://www.pegatroncorp.com/download/New_Duke_PC_Driver_0705.zip">http://www.pegatroncorp.com/download/New_Duke_PC_Driver_0705.zip</a> (ZIP download)</td>
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/Paint.java b/graphics/java/android/graphics/Paint.java
index 7d99fec..8da20f2 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1197,14 +1197,14 @@
return 0f;
}
if (!mHasCompatScaling) {
- return native_measureText(text, index, count);
+ return (float) Math.ceil(native_measureText(text, index, count));
}
final float oldSize = getTextSize();
setTextSize(oldSize*mCompatScaling);
float w = native_measureText(text, index, count);
setTextSize(oldSize);
- return w*mInvCompatScaling;
+ return (float) Math.ceil(w*mInvCompatScaling);
}
private native float native_measureText(char[] text, int index, int count);
@@ -1229,14 +1229,14 @@
return 0f;
}
if (!mHasCompatScaling) {
- return native_measureText(text, start, end);
+ return (float) Math.ceil(native_measureText(text, start, end));
}
final float oldSize = getTextSize();
setTextSize(oldSize*mCompatScaling);
float w = native_measureText(text, start, end);
setTextSize(oldSize);
- return w*mInvCompatScaling;
+ return (float) Math.ceil(w*mInvCompatScaling);
}
private native float native_measureText(String text, int start, int end);
@@ -1256,12 +1256,14 @@
return 0f;
}
- if (!mHasCompatScaling) return native_measureText(text);
+ if (!mHasCompatScaling) {
+ return (float) Math.ceil(native_measureText(text));
+ }
final float oldSize = getTextSize();
setTextSize(oldSize*mCompatScaling);
float w = native_measureText(text);
setTextSize(oldSize);
- return w*mInvCompatScaling;
+ return (float) Math.ceil(w*mInvCompatScaling);
}
private native float native_measureText(String text);
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/Caches.cpp b/libs/hwui/Caches.cpp
index 5befb95..a1cc2e8 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -50,9 +50,9 @@
Caches::Caches(): Singleton<Caches>(), mExtensions(Extensions::getInstance()), mInitialized(false) {
init();
initFont();
- initExtensions();
initConstraints();
initProperties();
+ initExtensions();
mDebugLevel = readDebugLevel();
ALOGD("Enabling debug mode %d", mDebugLevel);
@@ -103,15 +103,21 @@
void Caches::initExtensions() {
if (mExtensions.hasDebugMarker()) {
eventMark = glInsertEventMarkerEXT;
- startMark = glPushGroupMarkerEXT;
- endMark = glPopGroupMarkerEXT;
+ if ((drawDeferDisabled || drawReorderDisabled)) {
+ startMark = glPushGroupMarkerEXT;
+ endMark = glPopGroupMarkerEXT;
+ } else {
+ startMark = startMarkNull;
+ endMark = endMarkNull;
+ }
+
} else {
eventMark = eventMarkNull;
startMark = startMarkNull;
endMark = endMarkNull;
}
- if (mExtensions.hasDebugLabel()) {
+ if (mExtensions.hasDebugLabel() && (drawDeferDisabled || drawReorderDisabled)) {
setLabel = glLabelObjectEXT;
getLabel = glGetObjectLabelEXT;
} else {
@@ -164,6 +170,20 @@
debugStencilClip = kStencilHide;
}
+ if (property_get(PROPERTY_DISABLE_DRAW_DEFER, property, "false")) {
+ drawDeferDisabled = !strcasecmp(property, "true");
+ INIT_LOGD(" Draw defer %s", drawDeferDisabled ? "disabled" : "enabled");
+ } else {
+ INIT_LOGD(" Draw defer enabled");
+ }
+
+ if (property_get(PROPERTY_DISABLE_DRAW_REORDER, property, "false")) {
+ drawReorderDisabled = !strcasecmp(property, "true");
+ INIT_LOGD(" Draw reorder %s", drawReorderDisabled ? "disabled" : "enabled");
+ } else {
+ INIT_LOGD(" Draw reorder enabled");
+ }
+
return (prevDebugLayersUpdates != debugLayersUpdates) ||
(prevDebugOverdraw != debugOverdraw) ||
(prevDebugStencilClip != debugStencilClip);
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index c35ad883..ca699d5 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -240,6 +240,9 @@
Program* currentProgram;
bool scissorEnabled;
+ bool drawDeferDisabled;
+ bool drawReorderDisabled;
+
// VBO to draw with
GLuint meshBuffer;
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 8962964..a4e9950 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -146,6 +146,8 @@
if (isEmpty()) return status; // nothing to flush
DEFER_LOGD("--flushing");
+ renderer.eventMark("Flush");
+
DrawModifiers restoreDrawModifiers = renderer.getDrawModifiers();
int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
int opCount = 0;
@@ -166,6 +168,7 @@
}
DEFER_LOGD("--flushed, drew %d batches (total %d ops)", mBatches.size(), opCount);
+
renderer.restoreToCount(restoreTo);
renderer.setDrawModifiers(restoreDrawModifiers);
clear();
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 97812d4..4a72477 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -147,7 +147,8 @@
if (!renderer.storeDisplayState(state)) {
// op wasn't quick-rejected, so defer
- deferredList->add(this, renderer.disallowReorder());
+ deferredList->add(this, renderer.getCaches().drawReorderDisabled);
+ onDrawOpDeferred(renderer);
}
return DrawGlInfo::kStatusDone;
@@ -156,6 +157,9 @@
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
bool caching, int multipliedAlpha) = 0;
+ virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+ }
+
// returns true if bounds exist
virtual bool getLocalBounds(Rect& localBounds) { return false; }
@@ -176,8 +180,8 @@
*/
DeferredDisplayState state;
protected:
- SkPaint* getPaint(OpenGLRenderer& renderer) {
- return renderer.filterPaint(mPaint);
+ SkPaint* getPaint(OpenGLRenderer& renderer, bool alwaysCopy = false) {
+ return renderer.filterPaint(mPaint, alwaysCopy);
}
SkPaint* mPaint; // should be accessed via getPaint() when applying
@@ -643,16 +647,13 @@
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
bool caching, int multipliedAlpha) {
- SkPaint* paint = getPaint(renderer);
- int oldAlpha = -1;
- if (caching && multipliedAlpha < 255) {
- oldAlpha = paint->getAlpha();
+ bool makeCopy = caching && multipliedAlpha < 255;
+ SkPaint* paint = getPaint(renderer, makeCopy);
+ if (makeCopy) {
+ // The paint is safe to modify since we're working on a copy
paint->setAlpha(multipliedAlpha);
}
status_t ret = renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top, paint);
- if (oldAlpha >= 0) {
- paint->setAlpha(oldAlpha);
- }
return ret;
}
@@ -1084,6 +1085,12 @@
OP_LOG("Draw some text, %d bytes", mBytesCount);
}
+ virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+ SkPaint* paint = getPaint(renderer);
+ FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
+ fontRenderer.precache(paint, mText, mCount, mat4::identity());
+ }
+
virtual DeferredDisplayList::OpBatchId getBatchId() {
return mPaint->getColor() == 0xff000000 ?
DeferredDisplayList::kOpBatch_Text :
@@ -1143,9 +1150,33 @@
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);
+ }
+
+ /*
+ * When this method is invoked the state field is initialized to have the
+ * final rendering state. We can thus use it to process data as it will be
+ * used at draw time.
+ */
+ virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
+ SkPaint* paint = getPaint(renderer);
+ FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
+ const bool pureTranslate = state.mMatrix.isPureTranslate();
+ fontRenderer.precache(paint, mText, mCount,
+ pureTranslate ? mat4::identity() : state.mMatrix);
}
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..b011443 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -403,11 +403,7 @@
DrawOp* op = new (alloc()) DrawTextOnPathOp(text, bytesCount, count, path,
hOffset, vOffset, paint);
- if (addDrawOp(op)) {
- // precache if draw operation is visible
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, text, count, *mSnapshot->transform);
- }
+ addDrawOp(op);
return DrawGlInfo::kStatusDone;
}
@@ -420,11 +416,7 @@
paint = refPaint(paint);
DrawOp* op = new (alloc()) DrawPosTextOp(text, bytesCount, count, positions, paint);
- if (addDrawOp(op)) {
- // precache if draw operation is visible
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, text, count, *mSnapshot->transform);
- }
+ addDrawOp(op);
return DrawGlInfo::kStatusDone;
}
@@ -439,11 +431,7 @@
paint = refPaint(paint);
DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count, x, y, positions, paint, length);
- if (addDrawOp(op)) {
- // precache if draw operation is visible
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, text, count, *mSnapshot->transform);
- }
+ addDrawOp(op);
return DrawGlInfo::kStatusDone;
}
@@ -513,17 +501,15 @@
addOpInternal(op);
}
-bool DisplayListRenderer::addDrawOp(DrawOp* op) {
- bool rejected = false;
+void DisplayListRenderer::addDrawOp(DrawOp* op) {
Rect localBounds;
if (op->getLocalBounds(localBounds)) {
- rejected = quickRejectNoScissor(localBounds.left, localBounds.top,
+ bool rejected = quickRejectNoScissor(localBounds.left, localBounds.top,
localBounds.right, localBounds.bottom);
op->setQuickRejected(rejected);
}
mHasDrawOps = true;
addOpInternal(op);
- return !rejected;
}
}; // namespace uirenderer
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index bff3b97..38619bf 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -195,7 +195,7 @@
LinearAllocator& alloc() { return mDisplayListData->allocator; }
void addStateOp(StateOp* op);
- bool addDrawOp(DrawOp* op); // returns true if op not rejected
+ void addDrawOp(DrawOp* op);
void addOpInternal(DisplayListOp* op) {
insertRestoreToCount();
insertTranslate();
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/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index bc660cd..14c7c39 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -123,13 +123,9 @@
return &mLayer->region;
}
-// TODO: This implementation is flawed and can generate T-junctions
-// in the mesh, which will in turn produce cracks when the
-// mesh is rotated/skewed. The easiest way to fix this would
-// be, for each row, to add new vertices shared with the previous
-// row when the two rows share an edge.
-// In practice, T-junctions do not appear often so this has yet
-// to be fixed.
+// TODO: This implementation uses a very simple approach to fixing T-junctions which keeps the
+// results as rectangles, and is thus not necessarily efficient in the geometry
+// produced. Eventually, it may be better to develop triangle-based mechanism.
void LayerRenderer::generateMesh() {
if (mLayer->region.isRect() || mLayer->region.isEmpty()) {
if (mLayer->mesh) {
@@ -145,8 +141,14 @@
return;
}
+ // avoid T-junctions as they cause artifacts in between the resultant
+ // geometry when complex transforms occur.
+ // TODO: generate the safeRegion only if necessary based on drawing transform (see
+ // OpenGLRenderer::composeLayerRegion())
+ Region safeRegion = Region::createTJunctionFreeRegion(mLayer->region);
+
size_t count;
- const android::Rect* rects = mLayer->region.getArray(&count);
+ const android::Rect* rects = safeRegion.getArray(&count);
GLsizei elementCount = count * 6;
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index 5cec5a8..2d017df 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -39,6 +39,11 @@
// Matrix
///////////////////////////////////////////////////////////////////////////////
+const Matrix4& Matrix4::identity() {
+ static Matrix4 sIdentity;
+ return sIdentity;
+}
+
void Matrix4::loadIdentity() {
data[kScaleX] = 1.0f;
data[kSkewY] = 0.0f;
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index 46a5597..2fe96bc 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -152,6 +152,8 @@
void dump() const;
+ static const Matrix4& identity();
+
private:
mutable uint32_t mType;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 34d1c98..ff6f332 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -122,8 +122,6 @@
mFirstSnapshot = new Snapshot;
mScissorOptimizationDisabled = false;
- mDrawDeferDisabled = false;
- mDrawReorderDisabled = false;
}
OpenGLRenderer::~OpenGLRenderer() {
@@ -140,20 +138,6 @@
} else {
INIT_LOGD(" Scissor optimization enabled");
}
-
- if (property_get(PROPERTY_DISABLE_DRAW_DEFER, property, "false")) {
- mDrawDeferDisabled = !strcasecmp(property, "true");
- INIT_LOGD(" Draw defer %s", mDrawDeferDisabled ? "disabled" : "enabled");
- } else {
- INIT_LOGD(" Draw defer enabled");
- }
-
- if (property_get(PROPERTY_DISABLE_DRAW_REORDER, property, "false")) {
- mDrawReorderDisabled = !strcasecmp(property, "true");
- INIT_LOGD(" Draw reorder %s", mDrawReorderDisabled ? "disabled" : "enabled");
- } else {
- INIT_LOGD(" Draw reorder enabled");
- }
}
///////////////////////////////////////////////////////////////////////////////
@@ -462,6 +446,10 @@
// Debug
///////////////////////////////////////////////////////////////////////////////
+void OpenGLRenderer::eventMark(const char* name) const {
+ mCaches.eventMark(0, name);
+}
+
void OpenGLRenderer::startMark(const char* name) const {
mCaches.startMark(0, name);
}
@@ -1022,7 +1010,14 @@
// information about this implementation
if (CC_LIKELY(!layer->region.isEmpty())) {
size_t count;
- const android::Rect* rects = layer->region.getArray(&count);
+ const android::Rect* rects;
+ Region safeRegion;
+ if (CC_LIKELY(hasRectToRectTransform())) {
+ rects = layer->region.getArray(&count);
+ } else {
+ safeRegion = Region::createTJunctionFreeRegion(layer->region);
+ rects = safeRegion.getArray(&count);
+ }
const float alpha = layer->getAlpha() / 255.0f;
const float texX = 1.0f / float(layer->getWidth());
@@ -1650,13 +1645,13 @@
mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
} else {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, mIdentity);
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom);
}
}
void OpenGLRenderer::setupDrawModelViewIdentity(bool offset) {
- mCaches.currentProgram->set(mOrthoMatrix, mIdentity, *mSnapshot->transform, offset);
+ mCaches.currentProgram->set(mOrthoMatrix, mat4::identity(), *mSnapshot->transform, offset);
}
void OpenGLRenderer::setupDrawModelView(float left, float top, float right, float bottom,
@@ -1674,7 +1669,7 @@
dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
}
} else {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, mIdentity);
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
if (mTrackDirtyRegions && dirty) dirtyLayer(left, top, right, bottom);
}
}
@@ -1709,7 +1704,7 @@
void OpenGLRenderer::setupDrawShaderIdentityUniforms() {
if (mDrawModifiers.mShader) {
mDrawModifiers.mShader->setupProgram(mCaches.currentProgram,
- mIdentity, *mSnapshot, &mTextureUnit);
+ mat4::identity(), *mSnapshot, &mTextureUnit);
}
}
@@ -1808,7 +1803,7 @@
// All the usual checks and setup operations (quickReject, setupDraw, etc.)
// will be performed by the display list itself
if (displayList && displayList->isRenderable()) {
- if (CC_UNLIKELY(mDrawDeferDisabled)) {
+ if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
return displayList->replay(*this, dirty, flags, 0);
}
@@ -2144,17 +2139,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) {
@@ -2555,10 +2550,14 @@
glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
}
+bool OpenGLRenderer::canSkipText(const SkPaint* paint) const {
+ float alpha = (mDrawModifiers.mHasShadow ? 1.0f : paint->getAlpha()) * mSnapshot->alpha;
+ return alpha == 0.0f && getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
+}
+
status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
const float* positions, SkPaint* paint) {
- if (text == NULL || count == 0 || mSnapshot->isIgnored() ||
- (paint->getAlpha() * mSnapshot->alpha == 0 && paint->getXfermode() == NULL)) {
+ if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
return DrawGlInfo::kStatusDone;
}
@@ -2576,7 +2575,7 @@
}
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, *mSnapshot->transform);
+ fontRenderer.setFont(paint, mat4::identity());
int alpha;
SkXfermode::Mode mode;
@@ -2630,8 +2629,7 @@
status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
float x, float y, const float* positions, SkPaint* paint, float length) {
- if (text == NULL || count == 0 || mSnapshot->isIgnored() ||
- (paint->getAlpha() * mSnapshot->alpha == 0 && paint->getXfermode() == NULL)) {
+ if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
return DrawGlInfo::kStatusDone;
}
@@ -2653,17 +2651,11 @@
return DrawGlInfo::kStatusDone;
}
-#if DEBUG_GLYPHS
- ALOGD("OpenGLRenderer drawText() with FontID=%d",
- SkTypeface::UniqueID(paint->getTypeface()));
-#endif
-
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, *mSnapshot->transform);
-
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);
y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
@@ -2673,16 +2665,18 @@
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
+ FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
+
if (CC_UNLIKELY(mDrawModifiers.mHasShadow)) {
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, alpha, mode,
- oldX, oldY);
+ fontRenderer.setFont(paint, mat4::identity());
+ drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
+ alpha, mode, oldX, oldY);
}
+ fontRenderer.setFont(paint, pureTranslate ? mat4::identity() : *mSnapshot->transform);
+
// Pick the appropriate texture filtering
- bool linearFilter = mSnapshot->transform->changesBounds();
- if (pureTranslate && !linearFilter) {
- linearFilter = 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);
@@ -2695,17 +2689,16 @@
setupDrawShader();
setupDrawBlending(true, mode);
setupDrawProgram();
- setupDrawModelView(x, y, x, y, pureTranslate, 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(pureTranslate);
+ setupDrawShaderUniforms(!isPerspective);
setupDrawTextGammaUniforms();
- const Rect* clip = pureTranslate ? mSnapshot->clipRect :
- (mSnapshot->hasPerspectiveTransform() ? NULL : &mSnapshot->getLocalClip());
+ const Rect* clip = isPerspective ? NULL : mSnapshot->clipRect;
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
const bool hasActiveLayer = hasLayer();
@@ -2722,7 +2715,7 @@
}
if (status && hasActiveLayer) {
- if (!pureTranslate) {
+ if (isPerspective) {
mSnapshot->transform->mapRect(bounds);
}
dirtyLayerUnchecked(bounds, getRegion());
@@ -2735,13 +2728,12 @@
status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, SkPath* path,
float hOffset, float vOffset, SkPaint* paint) {
- if (text == NULL || count == 0 || mSnapshot->isIgnored() ||
- (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
+ if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
return DrawGlInfo::kStatusDone;
}
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, *mSnapshot->transform);
+ fontRenderer.setFont(paint, mat4::identity());
int alpha;
SkXfermode::Mode mode;
@@ -2942,8 +2934,14 @@
mDrawModifiers.mPaintFilterSetBits = setBits & SkPaint::kAllFlags;
}
-SkPaint* OpenGLRenderer::filterPaint(SkPaint* paint) {
- if (CC_LIKELY(!mDrawModifiers.mHasDrawFilter || !paint)) return paint;
+SkPaint* OpenGLRenderer::filterPaint(SkPaint* paint, bool alwaysCopy) {
+ if (CC_LIKELY(!mDrawModifiers.mHasDrawFilter || !paint)) {
+ if (CC_UNLIKELY(alwaysCopy)) {
+ mFilteredPaint = *paint;
+ return &mFilteredPaint;
+ }
+ return paint;
+ }
uint32_t flags = paint->getFlags();
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 80f2081..1bfd3c0 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -259,7 +259,7 @@
virtual void resetPaintFilter();
virtual void setupPaintFilter(int clearBits, int setBits);
- SkPaint* filterPaint(SkPaint* paint);
+ SkPaint* filterPaint(SkPaint* paint, bool alwaysCopy = false);
bool disallowDeferral() {
// returns true if the OpenGLRenderer's state can be completely represented by
@@ -270,8 +270,6 @@
(mSnapshot->flags & Snapshot::kFlagFboTarget); // ensure we're not in a layer
}
- bool disallowReorder() { return mDrawReorderDisabled; }
-
bool storeDisplayState(DeferredDisplayState& state);
void restoreDisplayState(const DeferredDisplayState& state);
@@ -282,6 +280,10 @@
return mSnapshot->transform->isSimple();
}
+ Caches& getCaches() {
+ return mCaches;
+ }
+
/**
* Sets the alpha on the current snapshot. This alpha value will be modulated
* with other alpha values when drawing primitives.
@@ -291,6 +293,11 @@
}
/**
+ * Inserts a named event marker in the stream of GL commands.
+ */
+ void eventMark(const char* name) const;
+
+ /**
* Inserts a named group marker in the stream of GL commands. This marker
* can be used by tools to group commands into logical groups. A call to
* this method must always be followed later on by a call to endMark().
@@ -439,10 +446,6 @@
return false;
}
- Caches& getCaches() {
- return mCaches;
- }
-
private:
/**
* Discards the content of the framebuffer if supported by the driver.
@@ -779,6 +782,11 @@
void resetDrawTextureTexCoords(float u1, float v1, float u2, float v2);
/**
+ * Returns true if the specified paint will draw invisible text.
+ */
+ bool canSkipText(const SkPaint* paint) const;
+
+ /**
* Binds the specified texture. The texture unit must have been selected
* prior to calling this method.
*/
@@ -931,9 +939,6 @@
// List of layers to update at the beginning of a frame
Vector<Layer*> mLayerUpdates;
- // Indentity matrix
- const mat4 mIdentity;
-
// Indicates whether the clip must be restored
bool mDirtyClip;
@@ -955,8 +960,6 @@
// See PROPERTY_DISABLE_SCISSOR_OPTIMIZATION in
// Properties.h
bool mScissorOptimizationDisabled;
- bool mDrawDeferDisabled;
- bool mDrawReorderDisabled;
// No-ops start/endTiling when set
bool mSuppressTiling;
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
index 69ccdfa..395bbf6 100644
--- a/libs/hwui/PathTessellator.cpp
+++ b/libs/hwui/PathTessellator.cpp
@@ -109,7 +109,7 @@
}
if (isAA && halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
- halfStrokeWidth * inverseScaleX < 0.5f) {
+ 2 * halfStrokeWidth < inverseScaleX) {
maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
halfStrokeWidth = 0.0f;
}
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 1a75ea8..bf522b7 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -52,6 +52,14 @@
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];
+ mLookupTransform[SkMatrix::kMSkewY] = matrix.data[mat4::kSkewY];
+ if (!mLookupTransform.invert(&mInverseLookupTransform)) {
+ ALOGW("Could not query the inverse lookup transform for this font");
+ }
}
Font::~Font() {
@@ -71,6 +79,10 @@
hash = JenkinsHashMix(hash, android::hash_type(mStyle));
hash = JenkinsHashMix(hash, android::hash_type(mStrokeWidth));
hash = JenkinsHashMix(hash, int(mAntiAliasing));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleX]));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleY]));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMSkewX]));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMSkewY]));
return JenkinsHashWhiten(hash);
}
@@ -100,6 +112,26 @@
deltaInt = int(lhs.mAntiAliasing) - int(rhs.mAntiAliasing);
if (deltaInt != 0) return deltaInt;
+ if (lhs.mLookupTransform[SkMatrix::kMScaleX] <
+ rhs.mLookupTransform[SkMatrix::kMScaleX]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMScaleX] >
+ rhs.mLookupTransform[SkMatrix::kMScaleX]) return +1;
+
+ if (lhs.mLookupTransform[SkMatrix::kMScaleY] <
+ rhs.mLookupTransform[SkMatrix::kMScaleY]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMScaleY] >
+ rhs.mLookupTransform[SkMatrix::kMScaleY]) return +1;
+
+ if (lhs.mLookupTransform[SkMatrix::kMSkewX] <
+ rhs.mLookupTransform[SkMatrix::kMSkewX]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMSkewX] >
+ rhs.mLookupTransform[SkMatrix::kMSkewX]) return +1;
+
+ if (lhs.mLookupTransform[SkMatrix::kMSkewY] <
+ rhs.mLookupTransform[SkMatrix::kMSkewY]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMSkewY] >
+ rhs.mLookupTransform[SkMatrix::kMSkewY]) return +1;
+
return 0;
}
@@ -137,7 +169,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;
@@ -153,6 +185,33 @@
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) {
+ SkPoint p[4];
+ p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight);
+ p[1].iset(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop + glyph->mBitmapHeight);
+ p[2].iset(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop);
+ p[3].iset(glyph->mBitmapLeft, glyph->mBitmapTop);
+
+ mDescription.mInverseLookupTransform.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].x(), p[0].y(), u1, v2,
+ p[1].x(), p[1].y(), u2, v2,
+ p[2].x(), p[2].y(), u2, v1,
+ p[3].x(), p[3].y(), 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;
@@ -169,12 +228,6 @@
int32_t bX = 0, bY = 0;
for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
-#if DEBUG_FONT_RENDERER
- if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) {
- ALOGE("Skipping invalid index");
- continue;
- }
-#endif
uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
bitmap[bY * bitmapW + bX] = tempCol;
}
@@ -210,14 +263,14 @@
const float v2 = glyph->mBitmapMaxV;
mState->appendRotatedMeshQuad(
- position->fX + destination[0].fX,
- position->fY + destination[0].fY, u1, v2,
- position->fX + destination[1].fX,
- position->fY + destination[1].fY, u2, v2,
- position->fX + destination[2].fX,
- position->fY + destination[2].fY, u2, v1,
- position->fX + destination[3].fX,
- position->fY + destination[3].fY, u1, v1,
+ position->x() + destination[0].x(),
+ position->y() + destination[0].y(), u1, v2,
+ position->x() + destination[1].x(),
+ position->y() + destination[1].y(), u2, v2,
+ position->x() + destination[2].x(),
+ position->y() + destination[2].y(), u2, v1,
+ position->x() + destination[3].x(),
+ position->y() + destination[3].y(), u1, v1,
glyph->mCacheTexture);
}
@@ -226,16 +279,17 @@
ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
if (index >= 0) {
cachedGlyph = mCachedGlyphs.valueAt(index);
+
+ // Is the glyph still in texture cache?
+ if (!cachedGlyph->mIsValid) {
+ const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit,
+ &mDescription.mLookupTransform);
+ updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching);
+ }
} else {
cachedGlyph = cacheGlyph(paint, textUnit, precaching);
}
- // Is the glyph still in texture cache?
- if (!cachedGlyph->mIsValid) {
- const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit, NULL);
- updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching);
- }
-
return cachedGlyph;
}
@@ -285,7 +339,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);
}
@@ -306,7 +360,6 @@
}
void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
-
if (numGlyphs == 0 || text == NULL) {
return;
}
@@ -335,14 +388,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) {
@@ -355,24 +412,17 @@
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) {
- int penX = x + positions[(glyphsCount << 1)];
- int penY = y + positions[(glyphsCount << 1) + 1];
+ // 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];
- switch (align) {
- case SkPaint::kRight_Align:
- penX -= SkFixedToFloat(cachedGlyph->mAdvanceX);
- penY -= SkFixedToFloat(cachedGlyph->mAdvanceY);
- break;
- case SkPaint::kCenter_Align:
- penX -= SkFixedToFloat(cachedGlyph->mAdvanceX >> 1);
- penY -= SkFixedToFloat(cachedGlyph->mAdvanceY >> 1);
- default:
- break;
+ if (applyTransform) {
+ mTransform.mapPoint(penX, penY);
}
- (*this.*render)(cachedGlyph, penX, penY,
+ (*this.*render)(cachedGlyph, roundf(penX), roundf(penY),
bitmap, bitmapW, bitmapH, bounds, positions);
}
@@ -394,7 +444,7 @@
// Get the bitmap for the glyph
if (!skiaGlyph.fImage) {
- paint->findImage(skiaGlyph, NULL);
+ paint->findImage(skiaGlyph, &mDescription.mLookupTransform);
}
mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching);
@@ -410,24 +460,27 @@
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) {
CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
- const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, NULL);
- newGlyph->mGlyphIndex = skiaGlyph.fID;
+ const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform);
newGlyph->mIsValid = false;
+ newGlyph->mGlyphIndex = skiaGlyph.fID;
updateGlyphCache(paint, skiaGlyph, newGlyph, precaching);
@@ -438,13 +491,13 @@
FontDescription description(paint, matrix);
Font* font = state->mActiveFonts.get(description);
- if (font) {
- 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 7a64a67..b2382f4 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -69,6 +69,8 @@
uint8_t mStyle;
float mStrokeWidth;
bool mAntiAliasing;
+ SkMatrix mLookupTransform;
+ SkMatrix mInverseLookupTransform;
};
~Font();
@@ -123,6 +125,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);
@@ -136,6 +141,8 @@
// Cache of glyphs
DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
+
+ mat4 mTransform;
};
inline int strictly_order_type(const Font::FontDescription& lhs,
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 619e71c..726e6de 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -1359,7 +1359,7 @@
// always scan the file, so we can return the content://media Uri for existing files
return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
- false, true, false);
+ false, true, MediaScanner.isNoMediaPath(path));
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
return null;
diff --git a/packages/DefaultContainerService/AndroidManifest.xml b/packages/DefaultContainerService/AndroidManifest.xml
index 3dcd232..57c87e4 100644
--- a/packages/DefaultContainerService/AndroidManifest.xml
+++ b/packages/DefaultContainerService/AndroidManifest.xml
@@ -7,6 +7,8 @@
<uses-permission android:name="android.permission.ASEC_DESTROY"/>
<uses-permission android:name="android.permission.ASEC_MOUNT_UNMOUNT"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <!-- Used to improve MeasureUtils performance on emulated storage -->
+ <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
<uses-permission android:name="android.permission.ACCESS_ALL_EXTERNAL_STORAGE" />
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 731a09c..de77cac 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -16,16 +16,12 @@
package com.android.defcontainer;
-import com.android.internal.app.IMediaContainerService;
-import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
-
import android.app.IntentService;
import android.content.Intent;
-import android.content.pm.MacAuthenticatedInputStream;
import android.content.pm.ContainerEncryptionParams;
import android.content.pm.IPackageManager;
import android.content.pm.LimitedLengthInputStream;
+import android.content.pm.MacAuthenticatedInputStream;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
@@ -43,10 +39,16 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StatFs;
+import android.os.SystemClock;
import android.provider.Settings;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.Slog;
+import com.android.internal.app.IMediaContainerService;
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.content.PackageHelper;
+
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -228,9 +230,10 @@
public long calculateDirectorySize(String path) throws RemoteException {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- final File directory = new File(path);
- if (directory.exists() && directory.isDirectory()) {
- return MeasurementUtils.measureDirectory(path);
+ final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path));
+ if (dir.exists() && dir.isDirectory()) {
+ final String targetPath = dir.getAbsolutePath();
+ return MeasurementUtils.measureDirectory(targetPath);
} else {
return 0L;
}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 8d949a5..f3eecf2 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -365,6 +365,7 @@
if (DEBUG) {
Log.d(TAG, "Redrawing wallpaper");
}
+
if (mIsHwAccelerated) {
if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
@@ -626,13 +627,26 @@
}
mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+
+ int[] maxSize = new int[1];
+ Rect frame = surfaceHolder.getSurfaceFrame();
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
+ if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
+ mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+ mEgl.eglTerminate(mEglDisplay);
+ Log.e(GL_LOG_TAG, "requested texture size " +
+ frame.width() + "x" + frame.height() + " exceeds the support maximum of " +
+ maxSize[0] + "x" + maxSize[0]);
+ return false;
+ }
mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
int error = mEgl.eglGetError();
- if (error == EGL_BAD_NATIVE_WINDOW) {
- Log.e(GL_LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+ if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
+ Log.e(GL_LOG_TAG, "createWindowSurface returned " +
+ GLUtils.getEGLErrorString(error) + ".");
return false;
}
throw new RuntimeException("createWindowSurface failed " +
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/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 0e545c6..bb05325 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -1500,6 +1500,9 @@
return null;
}
+ WindowManager wm = null;
+ View view = null;
+
try {
Context context = mContext;
if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "addStartingWindow " + packageName
@@ -1559,8 +1562,8 @@
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
params.setTitle("Starting " + packageName);
- WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- View view = win.getDecorView();
+ wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ view = win.getDecorView();
if (win.isFloating()) {
// Whoops, there is no way to display an animation/preview
@@ -1590,6 +1593,11 @@
// failure loading resources because we are loading from an app
// on external storage that has been unmounted.
Log.w(TAG, appToken + " failed creating starting window", e);
+ } finally {
+ if (view != null && view.getParent() == null) {
+ Log.w(TAG, "view not successfully added to wm, removing view");
+ wm.removeViewImmediate(view);
+ }
}
return null;
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/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index fd5e79a..d84018f 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -528,7 +528,7 @@
return -1;
}
- public void registerUiTestAutomationService(IAccessibilityServiceClient serviceClient,
+ public void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient serviceClient,
AccessibilityServiceInfo accessibilityServiceInfo) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
@@ -543,6 +543,15 @@
+ "already registered!");
}
+ try {
+ owner.linkToDeath(userState.mUiAutomationSerivceOnwerDeathRecipient, 0);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Couldn't register for the death of a"
+ + " UiTestAutomationService!", re);
+ return;
+ }
+
+ userState.mUiAutomationServiceOwner = owner;
userState.mUiAutomationServiceClient = serviceClient;
// Set the temporary state.
@@ -1697,8 +1706,7 @@
if (!mIsAutomation) {
mContext.unbindService(this);
} else {
- userState.mUiAutomationService = null;
- userState.mUiAutomationServiceClient = null;
+ userState.destroyUiAutomationService();
}
removeServiceLocked(this, userState);
dispose();
@@ -1741,25 +1749,23 @@
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
- final int connectionId;
synchronized (mLock) {
- connectionId = mId;
mService = service;
mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
UserState userState = getUserStateLocked(mUserId);
addServiceLocked(this, userState);
- if (!userState.mBindingServices.contains(mComponentName)) {
- binderDied();
- } else {
+ if (userState.mBindingServices.contains(mComponentName)) {
userState.mBindingServices.remove(mComponentName);
onUserStateChangedLocked(userState);
+ try {
+ mServiceInterface.setConnection(this, mId);
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Error while setting connection for service: " + service, re);
+ }
+ } else {
+ binderDied();
}
}
- try {
- mServiceInterface.setConnection(this, connectionId);
- } catch (RemoteException re) {
- Slog.w(LOG_TAG, "Error while setting connection for service: " + service, re);
- }
}
@Override
@@ -2112,8 +2118,7 @@
// the state based on values in the settings database.
userState.mInstalledServices.remove(mAccessibilityServiceInfo);
userState.mEnabledServices.remove(mComponentName);
- userState.mUiAutomationService = null;
- userState.mUiAutomationServiceClient = null;
+ userState.destroyUiAutomationService();
}
onUserStateChangedLocked(userState);
}
@@ -2606,6 +2611,20 @@
private Service mUiAutomationService;
private IAccessibilityServiceClient mUiAutomationServiceClient;
+ private IBinder mUiAutomationServiceOwner;
+ private final DeathRecipient mUiAutomationSerivceOnwerDeathRecipient =
+ new DeathRecipient() {
+ @Override
+ public void binderDied() {
+ mUiAutomationServiceOwner.unlinkToDeath(
+ mUiAutomationSerivceOnwerDeathRecipient, 0);
+ mUiAutomationServiceOwner = null;
+ if (mUiAutomationService != null) {
+ mUiAutomationService.binderDied();
+ }
+ }
+ };
+
public UserState(int userId) {
mUserId = userId;
}
@@ -2626,8 +2645,6 @@
// Clear UI test automation state.
if (mUiAutomationService != null) {
mUiAutomationService.binderDied();
- mUiAutomationService = null;
- mUiAutomationServiceClient = null;
}
// Unbind all services.
@@ -2649,6 +2666,16 @@
mIsEnhancedWebAccessibilityEnabled = false;
mIsDisplayMagnificationEnabled = false;
}
+
+ public void destroyUiAutomationService() {
+ mUiAutomationService = null;
+ mUiAutomationServiceClient = null;
+ if (mUiAutomationServiceOwner != null) {
+ mUiAutomationServiceOwner.unlinkToDeath(
+ mUiAutomationSerivceOnwerDeathRecipient, 0);
+ mUiAutomationServiceOwner = null;
+ }
+ }
}
private final class AccessibilityContentObserver extends ContentObserver {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index f226683..d8bcf2cd 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -7419,29 +7419,63 @@
SystemProperties.set("ctl.start", "bugreport");
}
- public long inputDispatchingTimedOut(int pid, boolean aboveSystem) {
+ public static long getInputDispatchingTimeoutLocked(ActivityRecord r) {
+ return r != null ? getInputDispatchingTimeoutLocked(r.app) : KEY_DISPATCHING_TIMEOUT;
+ }
+
+ public static long getInputDispatchingTimeoutLocked(ProcessRecord r) {
+ if (r != null && (r.instrumentationClass != null || r.usingWrapper)) {
+ return INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT;
+ }
+ return KEY_DISPATCHING_TIMEOUT;
+ }
+
+
+ public long inputDispatchingTimedOut(int pid, final boolean aboveSystem) {
+ if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.FILTER_EVENTS);
+ }
+ ProcessRecord proc;
+ long timeout;
+ synchronized (this) {
+ synchronized (mPidsSelfLocked) {
+ proc = mPidsSelfLocked.get(pid);
+ }
+ timeout = getInputDispatchingTimeoutLocked(proc);
+ }
+
+ if (!inputDispatchingTimedOut(proc, null, null, aboveSystem)) {
+ return -1;
+ }
+
+ return timeout;
+ }
+
+ /**
+ * Handle input dispatching timeouts.
+ * Returns whether input dispatching should be aborted or not.
+ */
+ public boolean inputDispatchingTimedOut(final ProcessRecord proc,
+ final ActivityRecord activity, final ActivityRecord parent,
+ final boolean aboveSystem) {
if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission "
+ android.Manifest.permission.FILTER_EVENTS);
}
- ProcessRecord proc;
-
- // TODO: Unify this code with ActivityRecord.keyDispatchingTimedOut().
- synchronized (this) {
- synchronized (mPidsSelfLocked) {
- proc = mPidsSelfLocked.get(pid);
- }
- if (proc != null) {
+ if (proc != null) {
+ synchronized (this) {
if (proc.debugging) {
- return -1;
+ return false;
}
if (mDidDexOpt) {
// Give more time since we were dexopting.
mDidDexOpt = false;
- return -1;
+ return false;
}
if (proc.instrumentationClass != null) {
@@ -7449,19 +7483,18 @@
info.putString("shortMsg", "keyDispatchingTimedOut");
info.putString("longMsg", "Timed out while dispatching key event");
finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info);
- proc = null;
+ return true;
}
}
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ appNotResponding(proc, activity, parent, aboveSystem, "keyDispatchingTimedOut");
+ }
+ });
}
- if (proc != null) {
- appNotResponding(proc, null, null, aboveSystem, "keyDispatchingTimedOut");
- if (proc.instrumentationClass != null || proc.usingWrapper) {
- return INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT;
- }
- }
-
- return KEY_DISPATCHING_TIMEOUT;
+ return true;
}
public Bundle getTopActivityExtras(int requestType) {
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index cde17c9..054d213 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -867,51 +867,20 @@
}
public boolean keyDispatchingTimedOut() {
- // TODO: Unify this code with ActivityManagerService.inputDispatchingTimedOut().
ActivityRecord r;
- ProcessRecord anrApp = null;
+ ProcessRecord anrApp;
synchronized(service) {
r = getWaitingHistoryRecordLocked();
- if (r != null && r.app != null) {
- if (r.app.debugging) {
- return false;
- }
-
- if (service.mDidDexOpt) {
- // Give more time since we were dexopting.
- service.mDidDexOpt = false;
- return false;
- }
-
- if (r.app.instrumentationClass == null) {
- anrApp = r.app;
- } else {
- Bundle info = new Bundle();
- info.putString("shortMsg", "keyDispatchingTimedOut");
- info.putString("longMsg", "Timed out while dispatching key event");
- service.finishInstrumentationLocked(
- r.app, Activity.RESULT_CANCELED, info);
- }
- }
+ anrApp = r != null ? r.app : null;
}
-
- if (anrApp != null) {
- service.appNotResponding(anrApp, r, this, false, "keyDispatchingTimedOut");
- }
-
- return true;
+ return service.inputDispatchingTimedOut(anrApp, r, this, false);
}
/** Returns the key dispatching timeout for this application token. */
public long getKeyDispatchingTimeout() {
synchronized(service) {
ActivityRecord r = getWaitingHistoryRecordLocked();
- if (r != null && r.app != null
- && (r.app.instrumentationClass != null || r.app.usingWrapper)) {
- return ActivityManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT;
- }
-
- return ActivityManagerService.KEY_DISPATCHING_TIMEOUT;
+ return ActivityManagerService.getInputDispatchingTimeoutLocked(r);
}
}
diff --git a/test-runner/src/android/test/ProviderTestCase.java b/test-runner/src/android/test/ProviderTestCase.java
index 74cebee..1b323cf 100644
--- a/test-runner/src/android/test/ProviderTestCase.java
+++ b/test-runner/src/android/test/ProviderTestCase.java
@@ -68,7 +68,7 @@
mProviderContext = new IsolatedContext(mResolver, targetContextWrapper);
mProvider = mProviderClass.newInstance();
- mProvider.attachInfo(mProviderContext, null);
+ mProvider.attachInfoForTesting(mProviderContext, null);
assertNotNull(mProvider);
mResolver.addProvider(mProviderAuthority, getProvider());
}
@@ -108,7 +108,7 @@
DatabaseUtils.createDbFromSqlStatements(context, databaseName, databaseVersion, sql);
T provider = providerClass.newInstance();
- provider.attachInfo(context, null);
+ provider.attachInfoForTesting(context, null);
resolver.addProvider(authority, provider);
return resolver;
diff --git a/test-runner/src/android/test/ProviderTestCase2.java b/test-runner/src/android/test/ProviderTestCase2.java
index f7c4e03..dcd089da 100644
--- a/test-runner/src/android/test/ProviderTestCase2.java
+++ b/test-runner/src/android/test/ProviderTestCase2.java
@@ -140,7 +140,7 @@
mProviderContext = new IsolatedContext(mResolver, targetContextWrapper);
mProvider = mProviderClass.newInstance();
- mProvider.attachInfo(mProviderContext, null);
+ mProvider.attachInfoForTesting(mProviderContext, null);
assertNotNull(mProvider);
mResolver.addProvider(mProviderAuthority, getProvider());
}
@@ -219,7 +219,7 @@
DatabaseUtils.createDbFromSqlStatements(context, databaseName, databaseVersion, sql);
T provider = providerClass.newInstance();
- provider.attachInfo(context, null);
+ provider.attachInfoForTesting(context, null);
resolver.addProvider(authority, provider);
return resolver;
diff --git a/test-runner/src/android/test/RenamingDelegatingContext.java b/test-runner/src/android/test/RenamingDelegatingContext.java
index eee3ad7..3d763c7 100644
--- a/test-runner/src/android/test/RenamingDelegatingContext.java
+++ b/test-runner/src/android/test/RenamingDelegatingContext.java
@@ -63,7 +63,7 @@
if (allowAccessToExistingFilesAndDbs) {
mContext.makeExistingFilesAndDbsAccessible();
}
- mProvider.attachInfo(mContext, null);
+ mProvider.attachInfoForTesting(mContext, null);
return mProvider;
}
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 2a9016b..46a539e 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -30,447 +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>
@@ -480,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/tests/HwAccelerationTest/src/com/android/test/hwui/ScaledTextActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ScaledTextActivity.java
index e1bf3ea..a4e9b52 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ScaledTextActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ScaledTextActivity.java
@@ -54,6 +54,7 @@
public ScaledTextView(Context c) {
super(c);
+ setLayerType(LAYER_TYPE_HARDWARE, null);
mPath = makePath();
@@ -92,11 +93,28 @@
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- canvas.drawARGB(255, 255, 255, 255);
canvas.drawText(TEXT, 30.0f, 30.0f, mPaint);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ canvas.drawText(TEXT, 30.0f, 50.0f, mPaint);
+ mPaint.setTextAlign(Paint.Align.RIGHT);
+ canvas.drawText(TEXT, 30.0f, 70.0f, mPaint);
- canvas.translate(0.0f, 50.0f);
+ canvas.save();
+ canvas.translate(400.0f, 0.0f);
+ canvas.scale(3.0f, 3.0f);
+ mPaint.setTextAlign(Paint.Align.LEFT);
+ mPaint.setStrikeThruText(true);
+ canvas.drawText(TEXT, 30.0f, 30.0f, mPaint);
+ mPaint.setStrikeThruText(false);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ canvas.drawText(TEXT, 30.0f, 50.0f, mPaint);
+ mPaint.setTextAlign(Paint.Align.RIGHT);
+ canvas.drawText(TEXT, 30.0f, 70.0f, mPaint);
+ canvas.restore();
+
+ mPaint.setTextAlign(Paint.Align.LEFT);
+ canvas.translate(0.0f, 100.0f);
canvas.save();
canvas.scale(mScale, mScale);
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 59f1889..95d3041 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -202,6 +202,7 @@
/**
* Format of results:
* =================
+ * id=1
* bssid=68:7f:74:d7:1b:6e
* freq=2412
* level=-43
@@ -209,12 +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() {
- return doStringCommand("BSS RANGE=ALL MASK=0x1986");
+ public String scanResults(int sid) {
+ 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 7da4caa..900a38a 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -72,6 +72,7 @@
import android.provider.Settings;
import android.util.Log;
import android.util.LruCache;
+import android.text.TextUtils;
import com.android.internal.R;
import com.android.internal.app.IBatteryStats;
@@ -1369,6 +1370,7 @@
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
+ private static final String ID_STR = "id=";
private static final String BSSID_STR = "bssid=";
private static final String FREQ_STR = "freq=";
private static final String LEVEL_STR = "level=";
@@ -1376,8 +1378,12 @@
private static final String FLAGS_STR = "flags=";
private static final String SSID_STR = "ssid=";
private static final String DELIMITER_STR = "====";
+ private static final String END_STR = "####";
+
/**
* Format:
+ *
+ * id=1
* bssid=68:7f:76:d7:1a:6e
* freq=2412
* level=-44
@@ -1385,6 +1391,7 @@
* flags=[WPA2-PSK-CCMP][WPS][ESS]
* ssid=zfdy
* ====
+ * id=2
* bssid=68:5f:74:d7:1a:6f
* freq=5180
* level=-73
@@ -1393,16 +1400,43 @@
* ssid=zuby
* ====
*/
- private void setScanResults(String scanResults) {
+ private void setScanResults() {
String bssid = "";
int level = 0;
int freq = 0;
long tsf = 0;
String flags = "";
WifiSsid wifiSsid = null;
+ String scanResults;
+ String tmpResults;
+ StringBuffer scanResultsBuf = new StringBuffer();
+ int sid = 0;
- if (scanResults == null) {
- return;
+ while (true) {
+ tmpResults = mWifiNative.scanResults(sid);
+ if (TextUtils.isEmpty(tmpResults)) break;
+ scanResultsBuf.append(tmpResults);
+ scanResultsBuf.append("\n");
+ String[] lines = tmpResults.split("\n");
+ sid = -1;
+ for (int i=lines.length - 1; i >= 0; i--) {
+ 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) {
+ // Nothing to do
+ }
+ break;
+ }
+ }
+ if (sid == -1) break;
+ }
+
+ scanResults = scanResultsBuf.toString();
+ if (TextUtils.isEmpty(scanResults)) {
+ return;
}
synchronized(mScanResultCache) {
@@ -1439,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;
@@ -2407,7 +2441,7 @@
sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS);
break;
case WifiMonitor.SCAN_RESULTS_EVENT:
- setScanResults(mWifiNative.scanResults());
+ setScanResults();
sendScanResultsAvailableBroadcast();
mScanResultIsPending = false;
break;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index ad52585..7196c1b 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -335,7 +335,7 @@
deviceCapability = source.deviceCapability;
groupCapability = source.groupCapability;
status = source.status;
- wfdInfo = source.wfdInfo;
+ wfdInfo = new WifiP2pWfdInfo(source.wfdInfo);
}
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
index 4f40ebc..0900351 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
@@ -44,7 +44,7 @@
public WifiP2pDeviceList(WifiP2pDeviceList source) {
if (source != null) {
for (WifiP2pDevice d : source.getDeviceList()) {
- mDevices.put(d.deviceAddress, d);
+ mDevices.put(d.deviceAddress, new WifiP2pDevice(d));
}
}
}
@@ -53,7 +53,7 @@
public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) {
for (WifiP2pDevice device : devices) {
if (device.deviceAddress != null) {
- mDevices.put(device.deviceAddress, device);
+ mDevices.put(device.deviceAddress, new WifiP2pDevice(device));
}
}
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 1589fec..4a489d5 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -1839,7 +1839,7 @@
private void sendPeersChangedBroadcast() {
final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
- intent.putExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, mPeers);
+ intent.putExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, new WifiP2pDeviceList(mPeers));
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}