diff options
18 files changed, 1259 insertions, 506 deletions
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 036da9554d41..ce73ae1eb433 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1205,8 +1205,12 @@ </application> - <instrumentation - android:name="android.test.InstrumentationTestRunner" - android:targetPackage="com.android.frameworks.coretests" - android:label="Frameworks Core Tests" /> + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.frameworks.coretests" + android:label="Frameworks Core Tests" /> + + <instrumentation android:name="android.bluetooth.BluetoothTestRunner" + android:targetPackage="com.android.frameworks.coretests" + android:label="Bluetooth Tests" /> + </manifest> diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothRebootStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothRebootStressTest.java new file mode 100644 index 000000000000..33e9dd7fabc6 --- /dev/null +++ b/core/tests/coretests/src/android/bluetooth/BluetoothRebootStressTest.java @@ -0,0 +1,94 @@ +/* + * 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 android.bluetooth; + +import android.content.Context; +import android.test.InstrumentationTestCase; + +/** + * Instrumentation test case for stress test involving rebooting the device. + * <p> + * This test case tests that bluetooth is enabled after a device reboot. Because + * the device will reboot, the instrumentation must be driven by a script on the + * host side. + */ +public class BluetoothRebootStressTest extends InstrumentationTestCase { + private static final String TAG = "BluetoothRebootStressTest"; + private static final String OUTPUT_FILE = "BluetoothRebootStressTestOutput.txt"; + + private BluetoothTestUtils mTestUtils; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + Context context = getInstrumentation().getTargetContext(); + mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + mTestUtils.close(); + } + + /** + * Test method used to start the test by turning bluetooth on. + */ + public void testStart() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + mTestUtils.enable(adapter); + } + + /** + * Test method used in the middle iterations of the test to check if + * bluetooth is on. Does not toggle bluetooth after the check. Assumes that + * bluetooth has been turned on by {@code #testStart()} + */ + public void testMiddleNoToggle() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + + assertTrue(adapter.isEnabled()); + } + + /** + * Test method used in the middle iterations of the test to check if + * bluetooth is on. Toggles bluetooth after the check. Assumes that + * bluetooth has been turned on by {@code #testStart()} + */ + public void testMiddleToggle() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + + assertTrue(adapter.isEnabled()); + + mTestUtils.disable(adapter); + mTestUtils.enable(adapter); + } + + /** + * Test method used in the stop the test by turning bluetooth off. Assumes + * that bluetooth has been turned on by {@code #testStart()} + */ + public void testStop() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + + assertTrue(adapter.isEnabled()); + + mTestUtils.disable(adapter); + } +} diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java index cbd87140da33..d8d9eba14dc3 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java @@ -16,420 +16,66 @@ package android.bluetooth; -import android.app.Instrumentation; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.test.InstrumentationTestCase; -import android.util.Log; public class BluetoothStressTest extends InstrumentationTestCase { - private static final String TAG = "BluetoothEnablerStressTest"; + private static final String TAG = "BluetoothStressTest"; + private static final String OUTPUT_FILE = "BluetoothStressTestOutput.txt"; - /** - * Timeout for {@link BluetoothAdapter#disable()} in ms. - */ - private static final int DISABLE_TIMEOUT = 5000; - - /** - * Timeout for {@link BluetoothAdapter#enable()} in ms. - */ - private static final int ENABLE_TIMEOUT = 20000; - - /** - * Timeout for {@link BluetoothAdapter#setScanMode(int)} in ms. - */ - private static final int SET_SCAN_MODE_TIMEOUT = 5000; - - /** - * Timeout for {@link BluetoothAdapter#startDiscovery()} in ms. - */ - private static final int START_DISCOVERY_TIMEOUT = 5000; - - /** - * Timeout for {@link BluetoothAdapter#cancelDiscovery()} in ms. - */ - private static final int CANCEL_DISCOVERY_TIMEOUT = 5000; - - private static final int DISCOVERY_STARTED_FLAG = 1; - private static final int DISCOVERY_FINISHED_FLAG = 1 << 1; - private static final int SCAN_MODE_NONE_FLAG = 1 << 2; - private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3; - private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4; - private static final int STATE_OFF_FLAG = 1 << 5; - private static final int STATE_TURNING_ON_FLAG = 1 << 6; - private static final int STATE_ON_FLAG = 1 << 7; - private static final int STATE_TURNING_OFF_FLAG = 1 << 8; - - /** - * Time between polls in ms. - */ - private static final int POLL_TIME = 100; - - private static final int ENABLE_ITERATIONS = 100; - private static final int DISCOVERABLE_ITERATIONS = 1000; - private static final int SCAN_ITERATIONS = 1000; - - private Context mContext; - - private Instrumentation mInstrumentation; - - private class BluetoothReceiver extends BroadcastReceiver { - private int mFiredFlags = 0; - - @Override - public void onReceive(Context context, Intent intent) { - synchronized (this) { - if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) { - mFiredFlags |= DISCOVERY_STARTED_FLAG; - } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) { - mFiredFlags |= DISCOVERY_FINISHED_FLAG; - } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) { - int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, - BluetoothAdapter.ERROR); - assertNotSame(mode, BluetoothAdapter.ERROR); - switch (mode) { - case BluetoothAdapter.SCAN_MODE_NONE: - mFiredFlags |= SCAN_MODE_NONE_FLAG; - break; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE: - mFiredFlags |= SCAN_MODE_CONNECTABLE_FLAG; - break; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: - mFiredFlags |= SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG; - break; - } - } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.ERROR); - assertNotSame(state, BluetoothAdapter.ERROR); - switch (state) { - case BluetoothAdapter.STATE_OFF: - mFiredFlags |= STATE_OFF_FLAG; - break; - case BluetoothAdapter.STATE_TURNING_ON: - mFiredFlags |= STATE_TURNING_ON_FLAG; - break; - case BluetoothAdapter.STATE_ON: - mFiredFlags |= STATE_ON_FLAG; - break; - case BluetoothAdapter.STATE_TURNING_OFF: - mFiredFlags |= STATE_TURNING_OFF_FLAG; - break; - } - } - } - } - - public int getFiredFlags() { - synchronized (this) { - return mFiredFlags; - } - } - - public void resetFiredFlags() { - synchronized (this) { - mFiredFlags = 0; - } - } - } - - private BluetoothReceiver mReceiver = new BluetoothReceiver(); + private BluetoothTestUtils mTestUtils; @Override protected void setUp() throws Exception { super.setUp(); - mInstrumentation = getInstrumentation(); - mContext = mInstrumentation.getTargetContext(); - - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); - filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); - filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); - filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - mContext.registerReceiver(mReceiver, filter); + Context context = getInstrumentation().getTargetContext(); + mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE); } @Override protected void tearDown() throws Exception { super.tearDown(); - mContext.unregisterReceiver(mReceiver); + mTestUtils.close(); } - public void testEnableDisable() { + public void testEnable() { + int iterations = BluetoothTestRunner.sEnableIterations; BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - for (int i = 0; i < ENABLE_ITERATIONS; i++) { - Log.i(TAG, "Enable iteration " + (i + 1) + " of " + ENABLE_ITERATIONS); - enable(adapter); - disable(adapter); + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("enable iteration " + (i + 1) + " of " + iterations); + mTestUtils.enable(adapter); + mTestUtils.disable(adapter); } } public void testDiscoverable() { + int iterations = BluetoothTestRunner.sDiscoverableIterations; BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - enable(adapter); + mTestUtils.enable(adapter); - for (int i = 0; i < DISCOVERABLE_ITERATIONS; i++) { - Log.i(TAG, "Discoverable iteration " + (i + 1) + " of " + DISCOVERABLE_ITERATIONS); - discoverable(adapter); - undiscoverable(adapter); + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("discoverable iteration " + (i + 1) + " of " + iterations); + mTestUtils.discoverable(adapter); + mTestUtils.undiscoverable(adapter); } - disable(adapter); + mTestUtils.disable(adapter); } public void testScan() { + int iterations = BluetoothTestRunner.sScanIterations; BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - enable(adapter); - - for (int i = 0; i < SCAN_ITERATIONS; i++) { - Log.i(TAG, "Scan iteration " + (i + 1) + " of " + SCAN_ITERATIONS); - startScan(adapter); - stopScan(adapter); - } - - disable(adapter); - } - - private void disable(BluetoothAdapter adapter) { - int mask = STATE_TURNING_OFF_FLAG | STATE_OFF_FLAG | SCAN_MODE_NONE_FLAG; - mReceiver.resetFiredFlags(); - - int state = adapter.getState(); - switch (state) { - case BluetoothAdapter.STATE_OFF: - assertFalse(adapter.isEnabled()); - return; - case BluetoothAdapter.STATE_ON: - assertTrue(adapter.isEnabled()); - assertTrue(adapter.disable()); - break; - case BluetoothAdapter.STATE_TURNING_ON: - assertFalse(adapter.isEnabled()); - assertTrue(adapter.disable()); - break; - case BluetoothAdapter.STATE_TURNING_OFF: - assertFalse(adapter.isEnabled()); - mask = 0; // Don't check for received intents since we might have missed them. - break; - default: - fail("disable() invalid state: " + state); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < DISABLE_TIMEOUT) { - state = adapter.getState(); - if (state == BluetoothAdapter.STATE_OFF) { - assertFalse(adapter.isEnabled()); - if ((mReceiver.getFiredFlags() & mask) == mask) { - mReceiver.resetFiredFlags(); - return; - } - } else { - assertFalse(adapter.isEnabled()); - assertEquals(BluetoothAdapter.STATE_TURNING_OFF, state); - } - sleep(POLL_TIME); - } - - int firedFlags = mReceiver.getFiredFlags(); - mReceiver.resetFiredFlags(); - fail("disable() timeout: " + - "state=" + state + " (expected " + BluetoothAdapter.STATE_OFF + ") " + - "flags=" + firedFlags + " (expected " + mask + ")"); - } - - private void enable(BluetoothAdapter adapter) { - int mask = STATE_TURNING_ON_FLAG | STATE_ON_FLAG | SCAN_MODE_CONNECTABLE_FLAG; - mReceiver.resetFiredFlags(); - - int state = adapter.getState(); - switch (state) { - case BluetoothAdapter.STATE_ON: - assertTrue(adapter.isEnabled()); - return; - case BluetoothAdapter.STATE_OFF: - case BluetoothAdapter.STATE_TURNING_OFF: - assertFalse(adapter.isEnabled()); - assertTrue(adapter.enable()); - break; - case BluetoothAdapter.STATE_TURNING_ON: - assertFalse(adapter.isEnabled()); - mask = 0; // Don't check for received intents since we might have missed them. - break; - default: - fail("enable() invalid state: state=" + state); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < ENABLE_TIMEOUT) { - state = adapter.getState(); - if (state == BluetoothAdapter.STATE_ON) { - assertTrue(adapter.isEnabled()); - if ((mReceiver.getFiredFlags() & mask) == mask) { - mReceiver.resetFiredFlags(); - return; - } - } else { - assertFalse(adapter.isEnabled()); - assertEquals(BluetoothAdapter.STATE_TURNING_ON, state); - } - sleep(POLL_TIME); - } - - int firedFlags = mReceiver.getFiredFlags(); - mReceiver.resetFiredFlags(); - fail("enable() timeout: " + - "state=" + state + " (expected " + BluetoothAdapter.STATE_OFF + ") " + - "flags=" + firedFlags + " (expected " + mask + ")"); - } - - private void discoverable(BluetoothAdapter adapter) { - int mask = SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG; - mReceiver.resetFiredFlags(); + mTestUtils.enable(adapter); - if (!adapter.isEnabled()) { - fail("discoverable() bluetooth not enabled"); + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("scan iteration " + (i + 1) + " of " + iterations); + mTestUtils.startScan(adapter); + mTestUtils.stopScan(adapter); } - int scanMode = adapter.getScanMode(); - if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - return; - } - - assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE); - assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)); - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) { - scanMode = adapter.getScanMode(); - if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - if ((mReceiver.getFiredFlags() & mask) == mask) { - mReceiver.resetFiredFlags(); - return; - } - } else { - assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE); - } - sleep(POLL_TIME); - } - - int firedFlags = mReceiver.getFiredFlags(); - mReceiver.resetFiredFlags(); - fail("discoverable() timeout: " + - "scanMode=" + scanMode + " (expected " + - BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE + ") " + - "flags=" + firedFlags + " (expected " + mask + ")"); - } - - private void undiscoverable(BluetoothAdapter adapter) { - int mask = SCAN_MODE_CONNECTABLE_FLAG; - mReceiver.resetFiredFlags(); - - if (!adapter.isEnabled()) { - fail("undiscoverable() bluetooth not enabled"); - } - - int scanMode = adapter.getScanMode(); - if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) { - return; - } - - assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); - assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE)); - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) { - scanMode = adapter.getScanMode(); - if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) { - if ((mReceiver.getFiredFlags() & mask) == mask) { - mReceiver.resetFiredFlags(); - return; - } - } else { - assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); - } - sleep(POLL_TIME); - } - - int firedFlags = mReceiver.getFiredFlags(); - mReceiver.resetFiredFlags(); - fail("undiscoverable() timeout: " + - "scanMode=" + scanMode + " (expected " + - BluetoothAdapter.SCAN_MODE_CONNECTABLE + ") " + - "flags=" + firedFlags + " (expected " + mask + ")"); - } - - private void startScan(BluetoothAdapter adapter) { - int mask = DISCOVERY_STARTED_FLAG; - mReceiver.resetFiredFlags(); - - if (!adapter.isEnabled()) { - fail("startScan() bluetooth not enabled"); - } - - if (adapter.isDiscovering()) { - return; - } - - assertTrue(adapter.startDiscovery()); - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < START_DISCOVERY_TIMEOUT) { - if (adapter.isDiscovering() && ((mReceiver.getFiredFlags() & mask) == mask)) { - mReceiver.resetFiredFlags(); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = mReceiver.getFiredFlags(); - mReceiver.resetFiredFlags(); - fail("startScan() timeout: " + - "isDiscovering=" + adapter.isDiscovering() + " " + - "flags=" + firedFlags + " (expected " + mask + ")"); - } - - private void stopScan(BluetoothAdapter adapter) { - int mask = DISCOVERY_FINISHED_FLAG; - mReceiver.resetFiredFlags(); - - if (!adapter.isEnabled()) { - fail("stopScan() bluetooth not enabled"); - } - - if (!adapter.isDiscovering()) { - return; - } - - // TODO: put assertTrue() around cancelDiscovery() once it starts - // returning true. - adapter.cancelDiscovery(); - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < CANCEL_DISCOVERY_TIMEOUT) { - if (!adapter.isDiscovering() && ((mReceiver.getFiredFlags() & mask) == mask)) { - mReceiver.resetFiredFlags(); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = mReceiver.getFiredFlags(); - mReceiver.resetFiredFlags(); - fail("stopScan() timeout: " + - "isDiscovering=" + adapter.isDiscovering() + " " + - "flags=" + firedFlags + " (expected " + mask + ")"); - } - - private void sleep(long time) { - try { - Thread.sleep(time); - } catch (InterruptedException e) { - } + mTestUtils.disable(adapter); } } diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java new file mode 100644 index 000000000000..cf0ff99d1283 --- /dev/null +++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java @@ -0,0 +1,73 @@ +/* + * 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 android.bluetooth; + +import junit.framework.TestSuite; + +import android.os.Bundle; +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; + +public class BluetoothTestRunner extends InstrumentationTestRunner { + public static int sEnableIterations = 100; + public static int sDiscoverableIterations = 1000; + public static int sScanIterations = 1000; + + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(BluetoothStressTest.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return BluetoothTestRunner.class.getClassLoader(); + } + + @Override + public void onCreate(Bundle arguments) { + super.onCreate(arguments); + + String val = arguments.getString("enable_iterations"); + if (val != null) { + try { + sEnableIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("discoverable_iterations"); + if (val != null) { + try { + sDiscoverableIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("scan_iterations"); + if (val != null) { + try { + sScanIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + } +} diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java new file mode 100644 index 000000000000..82de5098e66b --- /dev/null +++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java @@ -0,0 +1,441 @@ +/* + * 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 android.bluetooth; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Environment; +import android.util.Log; + +import junit.framework.Assert; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +public class BluetoothTestUtils extends Assert { + + /** + * Timeout for {@link BluetoothAdapter#disable()} in ms. + */ + private static final int DISABLE_TIMEOUT = 5000; + + /** + * Timeout for {@link BluetoothAdapter#enable()} in ms. + */ + private static final int ENABLE_TIMEOUT = 20000; + + /** + * Timeout for {@link BluetoothAdapter#setScanMode(int)} in ms. + */ + private static final int SET_SCAN_MODE_TIMEOUT = 5000; + + /** + * Timeout for {@link BluetoothAdapter#startDiscovery()} in ms. + */ + private static final int START_DISCOVERY_TIMEOUT = 5000; + + /** + * Timeout for {@link BluetoothAdapter#cancelDiscovery()} in ms. + */ + private static final int CANCEL_DISCOVERY_TIMEOUT = 5000; + + private static final int DISCOVERY_STARTED_FLAG = 1; + private static final int DISCOVERY_FINISHED_FLAG = 1 << 1; + private static final int SCAN_MODE_NONE_FLAG = 1 << 2; + private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3; + private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4; + private static final int STATE_OFF_FLAG = 1 << 5; + private static final int STATE_TURNING_ON_FLAG = 1 << 6; + private static final int STATE_ON_FLAG = 1 << 7; + private static final int STATE_TURNING_OFF_FLAG = 1 << 8; + + /** + * Time between polls in ms. + */ + private static final int POLL_TIME = 100; + + private Context mContext; + + private BufferedWriter mOutputWriter; + + private String mOutputFile; + private String mTag; + + private class BluetoothReceiver extends BroadcastReceiver { + private int mFiredFlags = 0; + + @Override + public void onReceive(Context context, Intent intent) { + synchronized (this) { + if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) { + mFiredFlags |= DISCOVERY_STARTED_FLAG; + } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) { + mFiredFlags |= DISCOVERY_FINISHED_FLAG; + } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) { + int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, + BluetoothAdapter.ERROR); + assertNotSame(mode, BluetoothAdapter.ERROR); + switch (mode) { + case BluetoothAdapter.SCAN_MODE_NONE: + mFiredFlags |= SCAN_MODE_NONE_FLAG; + break; + case BluetoothAdapter.SCAN_MODE_CONNECTABLE: + mFiredFlags |= SCAN_MODE_CONNECTABLE_FLAG; + break; + case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: + mFiredFlags |= SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG; + break; + } + } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + assertNotSame(state, BluetoothAdapter.ERROR); + switch (state) { + case BluetoothAdapter.STATE_OFF: + mFiredFlags |= STATE_OFF_FLAG; + break; + case BluetoothAdapter.STATE_TURNING_ON: + mFiredFlags |= STATE_TURNING_ON_FLAG; + break; + case BluetoothAdapter.STATE_ON: + mFiredFlags |= STATE_ON_FLAG; + break; + case BluetoothAdapter.STATE_TURNING_OFF: + mFiredFlags |= STATE_TURNING_OFF_FLAG; + break; + } + } + } + } + + public int getFiredFlags() { + synchronized (this) { + return mFiredFlags; + } + } + + public void resetFiredFlags() { + synchronized (this) { + mFiredFlags = 0; + } + } + } + + private BluetoothReceiver mReceiver = new BluetoothReceiver(); + + public BluetoothTestUtils(Context context, String tag) { + this(context, tag, null); + } + + public BluetoothTestUtils(Context context, String tag, String outputFile) { + mContext = context; + mTag = tag; + mOutputFile = outputFile; + + if (mOutputFile == null) { + mOutputWriter = null; + } else { + try { + mOutputWriter = new BufferedWriter(new FileWriter(new File( + Environment.getExternalStorageDirectory(), mOutputFile), true)); + } catch (IOException e) { + Log.w(mTag, "Test output file could not be opened", e); + mOutputWriter = null; + } + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); + filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); + filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + mContext.registerReceiver(mReceiver, filter); + } + + public void close() { + mContext.unregisterReceiver(mReceiver); + + if (mOutputWriter != null) { + try { + mOutputWriter.close(); + } catch (IOException e) { + Log.w(mTag, "Test output file could not be closed", e); + } + } + } + + public void enable(BluetoothAdapter adapter) { + int mask = STATE_TURNING_ON_FLAG | STATE_ON_FLAG | SCAN_MODE_CONNECTABLE_FLAG; + mReceiver.resetFiredFlags(); + + int state = adapter.getState(); + switch (state) { + case BluetoothAdapter.STATE_ON: + assertTrue(adapter.isEnabled()); + return; + case BluetoothAdapter.STATE_OFF: + case BluetoothAdapter.STATE_TURNING_OFF: + assertFalse(adapter.isEnabled()); + assertTrue(adapter.enable()); + break; + case BluetoothAdapter.STATE_TURNING_ON: + assertFalse(adapter.isEnabled()); + mask = 0; // Don't check for received intents since we might have missed them. + break; + default: + fail("enable() invalid state: state=" + state); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < ENABLE_TIMEOUT) { + state = adapter.getState(); + if (state == BluetoothAdapter.STATE_ON) { + assertTrue(adapter.isEnabled()); + if ((mReceiver.getFiredFlags() & mask) == mask) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("enable() completed in %d ms", + (System.currentTimeMillis() - s))); + return; + } + } else { + assertFalse(adapter.isEnabled()); + assertEquals(BluetoothAdapter.STATE_TURNING_ON, state); + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("enable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + state, BluetoothAdapter.STATE_ON, firedFlags, mask)); + } + + public void disable(BluetoothAdapter adapter) { + int mask = STATE_TURNING_OFF_FLAG | STATE_OFF_FLAG | SCAN_MODE_NONE_FLAG; + mReceiver.resetFiredFlags(); + + int state = adapter.getState(); + switch (state) { + case BluetoothAdapter.STATE_OFF: + assertFalse(adapter.isEnabled()); + return; + case BluetoothAdapter.STATE_ON: + assertTrue(adapter.isEnabled()); + assertTrue(adapter.disable()); + break; + case BluetoothAdapter.STATE_TURNING_ON: + assertFalse(adapter.isEnabled()); + assertTrue(adapter.disable()); + break; + case BluetoothAdapter.STATE_TURNING_OFF: + assertFalse(adapter.isEnabled()); + mask = 0; // Don't check for received intents since we might have missed them. + break; + default: + fail("disable() invalid state: state=" + state); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < DISABLE_TIMEOUT) { + state = adapter.getState(); + if (state == BluetoothAdapter.STATE_OFF) { + assertFalse(adapter.isEnabled()); + if ((mReceiver.getFiredFlags() & mask) == mask) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("disable() completed in %d ms", + (System.currentTimeMillis() - s))); + return; + } + } else { + assertFalse(adapter.isEnabled()); + assertEquals(BluetoothAdapter.STATE_TURNING_OFF, state); + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("disable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", + state, BluetoothAdapter.STATE_OFF, firedFlags, mask)); + } + + public void discoverable(BluetoothAdapter adapter) { + int mask = SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("discoverable() bluetooth not enabled"); + } + + int scanMode = adapter.getScanMode(); + if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + return; + } + + assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE); + assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)); + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) { + scanMode = adapter.getScanMode(); + if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { + if ((mReceiver.getFiredFlags() & mask) == mask) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("discoverable() completed in %d ms", + (System.currentTimeMillis() - s))); + return; + } + } else { + assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE); + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("discoverable() timeout: scanMode=%d (expected %d), flags=0x%x " + + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, + firedFlags, mask)); + } + + public void undiscoverable(BluetoothAdapter adapter) { + int mask = SCAN_MODE_CONNECTABLE_FLAG; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("undiscoverable() bluetooth not enabled"); + } + + int scanMode = adapter.getScanMode(); + if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) { + return; + } + + assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); + assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE)); + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) { + scanMode = adapter.getScanMode(); + if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) { + if ((mReceiver.getFiredFlags() & mask) == mask) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("undiscoverable() completed in %d ms", + (System.currentTimeMillis() - s))); + return; + } + } else { + assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d), flags=0x%x " + + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE, firedFlags, + mask)); + } + + public void startScan(BluetoothAdapter adapter) { + int mask = DISCOVERY_STARTED_FLAG; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("startScan() bluetooth not enabled"); + } + + if (adapter.isDiscovering()) { + return; + } + + assertTrue(adapter.startDiscovery()); + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < START_DISCOVERY_TIMEOUT) { + if (adapter.isDiscovering() && ((mReceiver.getFiredFlags() & mask) == mask)) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("startScan() completed in %d ms", + (System.currentTimeMillis() - s))); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", + adapter.isDiscovering(), firedFlags, mask)); + } + + public void stopScan(BluetoothAdapter adapter) { + int mask = DISCOVERY_FINISHED_FLAG; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("stopScan() bluetooth not enabled"); + } + + if (!adapter.isDiscovering()) { + return; + } + + // TODO: put assertTrue() around cancelDiscovery() once it starts returning true. + adapter.cancelDiscovery(); + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < CANCEL_DISCOVERY_TIMEOUT) { + if (!adapter.isDiscovering() && ((mReceiver.getFiredFlags() & mask) == mask)) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("stopScan() completed in %d ms", + (System.currentTimeMillis() - s))); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", + adapter.isDiscovering(), firedFlags, mask)); + + } + + public void writeOutput(String s) { + Log.i(mTag, s); + if (mOutputWriter == null) { + return; + } + try { + mOutputWriter.write(s + "\n"); + mOutputWriter.flush(); + } catch (IOException e) { + Log.w(mTag, "Could not write to output file", e); + } + } + + private void sleep(long time) { + try { + Thread.sleep(time); + } catch (InterruptedException e) { + } + } +} diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index d0318cfba096..35ce17e26eae 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -220,9 +220,16 @@ <li><a style="color:gray;">Accelerometer</a></li> </ul> </li> --> - <li><a href="<?cs var:toroot ?>guide/topics/location/index.html"> - <span class="en">Location and Maps</span> - </a></li> + <li class="toggle-list"> + <div><a href="<?cs var:toroot ?>guide/topics/location/index.html"> + <span class="en">Location and Maps</span> + </a></div> + <ul> + <li><a href="<?cs var:toroot ?>guide/topics/location/obtaining-user-location.html"> + <span class="en">Obtaining User Location</span> + </a> <span class="new">new!</span></li> + </ul> + </li> <!--<li class="toggle-list"> <div><a style="color:gray;">Wireless Controls</a></div> <ul> diff --git a/docs/html/guide/topics/data/data-storage.jd b/docs/html/guide/topics/data/data-storage.jd index 293a0579ef67..e20d1ed22761 100644 --- a/docs/html/guide/topics/data/data-storage.jd +++ b/docs/html/guide/topics/data/data-storage.jd @@ -115,7 +115,7 @@ public class Calc extends Activity { public static final String PREFS_NAME = "MyPrefsFile"; @Override - protected void onCreate(Bundle state){ + protected void onCreate(Bundle state){ super.onCreate(state); . . . @@ -374,7 +374,7 @@ android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) onCreate()} me can execute a SQLite command to create tables in the database. For example:</p> <pre> -public class MyDbOpenHelper extends SQLiteOpenHelper { +public class DictionaryOpenHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 2; private static final String DICTIONARY_TABLE_NAME = "dictionary"; diff --git a/docs/html/guide/topics/fundamentals.jd b/docs/html/guide/topics/fundamentals.jd index f780e7c6bfce..6d6abd8b15c9 100644 --- a/docs/html/guide/topics/fundamentals.jd +++ b/docs/html/guide/topics/fundamentals.jd @@ -770,9 +770,9 @@ return to what that instance was doing before the new intent arrived. </p> <p> -For more on launch modes, see the description of the -<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> -element. +For more on launch modes, see the description of the <code><a +href="{@docRoot}guide/topics/manifest/activity-element.html#lmode"><activity></a></code> +element. </p> diff --git a/docs/html/guide/topics/location/index.jd b/docs/html/guide/topics/location/index.jd index e988ecbc454e..5f98902cf701 100644 --- a/docs/html/guide/topics/location/index.jd +++ b/docs/html/guide/topics/location/index.jd @@ -4,94 +4,63 @@ page.title=Location and Maps <div id="qv-wrapper"> <div id="qv"> - <h2>Location and Maps quickview</h2> + <h2>Quickview</h2> <ul> - <li>Android provides a location framework that your application can use to determine the device's location and bearing and register for updates.</li> - <li>A Google Maps external library is available that lets you display and manage Maps data.</li> + <li>Android provides a location framework that your application can use to determine the +device's location and bearing and register for updates</li> + <li>A Google Maps external library is available that lets you display and manage Maps data</li> </ul> - <h2>In this document</h2> + + <h2>Topics</h2> <ol> - <li><a href="#location">Location Services</a></li> - <li><a href="#maps">Google Maps External Library</a></li> + <li><a href="{@docRoot}guide/topics/location/obtaining-user-location.html">Obtaining User +Location</a></li> </ol> + <h2>See Also</h2> <ol> - <li><a href="http://code.google.com/android/add-ons/google-apis/index.html">Google APIs add-on download»</a></li> + <li><a +href="http://code.google.com/android/add-ons/google-apis/maps-overview.html">Google +Maps External Library »</a></li> </ol> </div> </div> -<p>Location- and maps-based applications and services are compelling for mobile device users. You can build these capabilities into your applications using the classes of the {@link android.location} package and the Google Maps external library. The sections below provide details. </p> +<p>Location and maps-based applications are compelling for mobile device users. You +can build these capabilities into your applications using the classes of the {@link +android.location} package and the Google Maps external library. The sections below provide details. +</p> <h2 id="location">Location Services</h2> <p>Android gives your applications access to the location services supported by -the device through the classes in the <code>android.location</code> package. The +the device through the classes in the {@code android.location} package. The central component of the location framework is the -{@link android.location.LocationManager} system service, which provides an API to -determine location and bearing if the underlying device (if it supports location -capabilities). </p> +{@link android.location.LocationManager} system service, which provides APIs to +determine location and bearing of the underlying device (if available). </p> -<p>As with other system services, you do not instantiate a LocationManager directly. -Rather, you request an LocationManager instance from the system by calling -{@link android.content.Context#getSystemService(String) getSystemService(Context.LOCATION_SERVICE)}. -The method returns a handle to a new LocationManager instance.</p> +<p>As with other system services, you do not instantiate a {@link android.location.LocationManager} +directly. Rather, you request an instance from the system by calling +{@link android.content.Context#getSystemService(String) +getSystemService(Context.LOCATION_SERVICE)}. The method returns a handle to a new {@link +android.location.LocationManager} instance.</p> -<p>Once your application has a handle to a LocationManager instance, your application -will be able to do three things:</p> +<p>Once your application has a {@link android.location.LocationManager}, your application +is able to do three things:</p> <ul> - <li>Query for the list of all LocationProviders known to the - LocationManager for its last known location.</li> - <li>Register/unregister for periodic updates of current location from a - LocationProvider (specified either by Criteria or name).</li> - <li>Register/unregister for a given Intent to be fired if the device comes - within a given proximity (specified by radius in meters) of a given - lat/long.</li> + <li>Query for the list of all {@link android.location.LocationProvider}s for the last known +user location.</li> + <li>Register/unregister for periodic updates of the user's current location from a + location provider (specified either by criteria or name).</li> + <li>Register/unregister for a given {@link android.content.Intent} to be fired if the device +comes within a given proximity (specified by radius in meters) of a given lat/long.</li> </ul> -<p>However, during initial development in the emulator, you may not have access to real -data from a real location provider (Network or GPS). In that case, it may be necessary to -spoof some data for your application using a mock location provider.</p> - -<p class="note"><strong>Note:</strong> If you've used mock LocationProviders in -previous versions of the SDK, you can no longer provide canned LocationProviders -in the /system/etc/location directory. These directories will be wiped during boot-up. -Please follow the new procedures outlined below.</p> - -<h3>Providing Mock Location Data</h3> - -<p>When testing your application on the Android emulator, there are a couple different -ways to send it some mock location data: you can use the DDMS tool or the "geo" command -option in the emulator console.</p> - -<h4 id="ddms">Using DDMS</h4> -<p>With the DDMS tool, you can simulate location data a few different ways:</p> -<ul> - <li>Manually send individual longitude/latitude coordinates to the device.</li> - <li>Use a GPX file describing a route for playback to the device.</li> - <li>Use a KML file describing individual placemarks for sequenced playback to the device.</li> -</ul> -<p>For more information on using DDMS to spoof location data, see the -<a href="{@docRoot}guide/developing/tools/ddms.html#emulator-control">Using DDMS guide</a>. - -<h4 id="geo">Using the "geo" command in the emulator console</h4> -<p>Launch your application in the Android emulator and open a terminal/console in -your SDK's <code>/tools</code> directory. Connect to the emulator console. Now you can use:</p> -<ul><li><code>geo fix</code> to send a fixed geo-location. - <p>This command accepts a longitude and latitude in decimal degrees, and - an optional altitude in meters. For example:</p> - <pre>geo fix -121.45356 46.51119 4392</pre> - </li> - <li><code>geo nmea</code> to send an NMEA 0183 sentence. - <p>This command accepts a single NMEA sentence of type '$GPGGA' (fix data) or '$GPRMC' (transit data). - For example:</p> - <pre>geo nmea $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62</pre> - </li> -</ul> +<p>For more information, read the guide to <a +href="{@docRoot}guide/topics/location/obtaining-user-location.html">Obtaining User +Location</a>.</p> -<p>For information about how to connect to the emulator console, see -<a href="{@docRoot}guide/developing/tools/emulator.html#console">Using the Emulator Console</a>.</p> <h2 id="maps">Google Maps External Library</h2> @@ -128,9 +97,9 @@ Google APIs add-on, visit</p> <p style="margin-left:2em;"><a href="http://code.google.com/android/add-ons/google-apis">http://code.google.com/android/add-ons/google-apis</a></p> -<p>For your convenience, the Google APIs add-on is also included in the Android -SDK. <!-- To learn now to use the Maps external library in your application, see -[[Using External Libraries]].--></p> +<p>For your convenience, the Google APIs add-on is also available as a downloadable component from +the Android SDK and AVD Manager (see <a href="{@docRoot}sdk/adding-components.html">Adding SDK +Components</a>).</p> <p class="note"><strong>Note:</strong> In order to display Google Maps data in a MapView, you must register with the Google Maps service and obtain a Maps API diff --git a/docs/html/guide/topics/location/obtaining-user-location.jd b/docs/html/guide/topics/location/obtaining-user-location.jd new file mode 100644 index 000000000000..bc782d2e9734 --- /dev/null +++ b/docs/html/guide/topics/location/obtaining-user-location.jd @@ -0,0 +1,454 @@ +page.title=Obtaining User Location +parent.title=Location and Maps +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + + <h2>Quickview</h2> + <ul> + <li>The Network Location Provider provides good location data without using GPS</li> + <li>Obtaining user location can consume a lot of battery, so be careful how +long you listen for updates</li> + </ul> + <h2>In this document</h2> + <ol> + <li><a href="#Challenges">Challenges in Determining User Location</a></li> + <li><a href="#Updates">Requesting Location Updates</a> + <ol> + <li><a href="#Permission">Requesting User Permissions</a></li> + </ol> + </li> + <li><a href="#BestPerformance">Defining a Model for the Best Performance</a> + <ol> + <li><a href="#Flow">Flow for obtaining user location</a></li> + <li><a href="#StartListening">Deciding when to start listening for updates</a></li> + <li><a href="#FastFix">Getting a fast fix with the last known location</a></li> + <li><a href="#StopListening">Deciding when to stop listening for updates</a></li> + <li><a href="#BestEstimate">Maintaining a current best estimate</a></li> + <li><a href="#Adjusting">Adjusting the model to save battery and data exchange</a></li> + </ol> + </li> + <li><a href="#MockData">Providing Mock Location Data</a></li> + </ol> + <h2>Key classes</h2> + <ol> + <li>{@link android.location.LocationManager}</li> + <li>{@link android.location.LocationListener}</li> + </ol> +</div> +</div> + + <p>Knowing where the user is allows your application to be smarter and deliver +better information to the user. When developing a location-aware application for Android, you can +utilize GPS and Android's Network Location Provider to acquire the user location. Although +GPS is most accurate, it only works outdoors, it quickly consumes battery power, and doesn't return +the location as quickly as users want. Android's Network Location Provider determines user location +using cell tower and Wi-Fi signals, providing location information in a way that +works indoors and outdoors, responds faster, and uses less battery power. To obtain the user +location in your application, you can use both GPS and the Network Location Provider, or just +one.</p> + + +<h2 id="Challenges">Challenges in Determining User Location</h2> + +<p>Obtaining user location from a mobile device can be complicated. There are several reasons +why a location reading (regardless of the source) can contain errors and be inaccurate. +Some sources of error in the user location include:</p> + +<ul> + <li><b>Multitude of location sources</b> + <p>GPS, Cell-ID, and Wi-Fi can each provide a clue to users location. Determining which to use +and trust is a matter of trade-offs in accuracy, speed, and battery-efficiency.</p> + </li> + <li><b>User movement</b> + <p>Because the user location changes, you must account for movement by re-estimating user +location every so often.</p> + </li> + <li><b>Varying accuracy</b> + <p>Location estimates coming from each location source are not consistent in their +accuracy. A location obtained 10 seconds ago from one source might be more accurate than the newest +location from another or same source.</p> + </li> +</ul> + + <p>These problems can make it difficult to obtain a reliable user location reading. This +document provides information to help you meet these challenges to obtain a reliable location +reading. It also provides ideas that you can use in your +application to provide the user with an accurate and responsive geo-location experience.</p> + + +<h2 id="Updates">Requesting Location Updates</h2> + + <p>Before addressing some of the location errors described above, here is an introduction to +how you can obtain user location on Android.</p> + + <p>Getting user location in Android works by means of callback. You indicate that you'd +like to receive location updates from the {@link android.location.LocationManager} ("Location +Manager") by calling {@link android.location.LocationManager#requestLocationUpdates +requestLocationUpdates()}, passing it a +{@link android.location.LocationListener}. Your {@link android.location.LocationListener} must +implement several callback methods that the Location Manager calls when the user location +changes or when the status of the service changes.</p> + +<p>For example, the following code shows how to define a {@link android.location.LocationListener} +and request location updates: + </p> + +<pre> +// Acquire a reference to the system Location Manager +LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); + +// Define a listener that responds to location updates +LocationListener locationListener = new LocationListener() { + public void onLocationChanged(Location location) { + // Called when a new location is found by the network location provider. + makeUseOfNewLocation(location); + } + + public void onStatusChanged(String provider, int status, Bundle extras) {} + + public void onProviderEnabled(String provider) {} + + public void onProviderDisabled(String provider) {} + }; + +// Register the listener with the Location Manager to receive location updates +locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener); +</pre> + + <p>The first parameter in {@link +android.location.LocationManager#requestLocationUpdates requestLocationUpdates()} is the type of +location provider to use (in this case, the Network Location Provider for cell tower and Wi-Fi +based location). You can control the frequency at which your listener receives updates +with the second and third parameter—the second is the minimum time interval between +notifications and the third is the minimum change in distance between notifications—setting +both to zero requests location notifications as frequently as possible. The last parameter is your +{@link android.location.LocationListener}, which receives callbacks for location updates.</p> + +<p>To request location updates from the GPS provider, +substitute <code>GPS_PROVIDER</code> for <code>NETWORK_PROVIDER</code>. You can also request +location updates from both the GPS and the Network Location Provider by calling {@link +android.location.LocationManager#requestLocationUpdates requestLocationUpdates()} twice—once +for <code>NETWORK_PROVIDER</code> and once for <code>GPS_PROVIDER</code>.</p> + + +<h3 id="Permission">Requesting User Permissions</h3> + +<p>In order to receive location updates from <code>NETWORK_PROVIDER</code> or +<code>GPS_PROVIDER</code>, you must request user permission by declaring either the {@code +ACCESS_COARSE_LOCATION} or {@code ACCESS_FINE_LOCATION} permission, respectively, in your Android +manifest file. For example:</p> + +<pre> +<manifest ... > + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + ... +</manifest> +</pre> + +<p>Without these permissions, your application will fail at runtime when requesting +location updates.</p> + +<p class="note"><strong>Note:</strong> If you are using both <code>NETWORK_PROVIDER</code> and +<code>GPS_PROVIDER</code>, then you need to request only the {@code ACCESS_FINE_LOCATION} +permission, because it includes permission for both providers. (Permission for {@code +ACCESS_COARSE_LOCATION} includes permission only for <code>NETWORK_PROVIDER</code>.)</p> + + +<h2 id="BestPerformance">Defining a Model for the Best Performance</h2> + + <p>Location-based applications are now commonplace, but due to the less than optimal +accuracy, user movement, the multitude of methods to obtain the location, and the desire to conserve +battery, getting user location is complicated. To overcome the obstacles of obtaining a good user +location while preserving battery power, you must define a consistent model that specifies how your +application obtains the user location. This model includes when you start and stop listening for +updates and when to use cached location data.</p> + + + <h3 id="Flow">Flow for obtaining user location</h3> + + <p>Here's the typical flow of procedures for obtaining the user location:</p> + + <ol> + <li>Start application.</li> + <li>Sometime later, start listening for updates from desired location providers.</li> + <li>Maintain a "current best estimate" of location by filtering out new, but less accurate +fixes.</li> + <li>Stop listening for location updates.</li> + <li>Take advantage of the last best location estimate.</li> + </ol> + + <p>Figure 1 demonstrates this model in a timeline that visualizes the period in which an +application is listening for location updates and the events that occur during that time.</p> + +<img src="{@docRoot}images/location/getting-location.png" alt="" /> +<p class="img-caption"><strong>Figure 1.</strong> A timeline representing the window in which an +application listens for location updates.</p> + + <p>This model of a window—during which location updates are received—frames many of +the decisions you need to make when adding location-based services to your application.</p> + + + <h3 id="StartListening">Deciding when to start listening for updates</h3> + + <p>You might want to start listening for location updates as soon as your application starts, or +only after users activate a certain feature. Be aware that long windows of listening for location +fixes can consume a lot of battery power, but short periods might not allow for sufficient +accuracy.</p> + + <p>As demonstrated above, you can begin listening for updates by calling {@link +android.location.LocationManager#requestLocationUpdates requestLocationUpdates()}:</p> + +<pre> +LocationProvider locationProvider = LocationManager.NETWORK_PROVIDER; +// Or, use GPS location data: +// LocationProvider locationProvider = LocationManager.GPS_PROVIDER; + +locationManager.requestLocationUpdates(locationProvider, 0, 0, locationListener); +</pre> + + + <h3 id="FastFix">Getting a fast fix with the last known location</h3> + + <p>The time it takes for your location listener to receive the first location fix is often too +long for users wait. Until a more accurate location is provided to your location listener, you +should utilize a cached location by calling {@link +android.location.LocationManager#getLastKnownLocation}:</p> +<pre> +LocationProvider locationProvider = LocationManager.NETWORK_PROVIDER; +// Or use LocationManager.GPS_PROVIDER + +Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider); +</pre> + + + <h3 id="StopListening">Deciding when to stop listening for updates</h3> + + <p>The logic of deciding when new fixes are no longer necessary might range from very simple to +very complex depending on your application. A short gap between when the location is acquired and +when the location is used, improves the accuracy of the estimate. Always beware that listening for a +long time consumes a lot of battery power, so as soon as you have the information you need, you +should stop +listening for updates by calling {@link android.location.LocationManager#removeUpdates}:</p> +<pre> +// Remove the listener you previously added +locationManager.removeUpdates(locationListener); +</pre> + + + <h3 id="BestEstimate">Maintaining a current best estimate</h3> + + <p>You might expect that the most recent location fix is the most accurate. +However, because the accuracy of a location fix varies, the most recent fix is not always the best. +You should include logic for choosing location fixes based on several criteria. The criteria also +varies depending on the use-cases of the application and field testing.</p> + + <p>Here are a few steps you can take to validate the accuracy of a location fix:</p> + <ul> + <li>Check if the location retrieved is significantly newer than the previous estimate.</li> + <li>Check if the accuracy claimed by the location is better or worse than the previous +estimate.</li> + <li>Check which provider the new location is from and determine if you trust it more.</li> + </ul> + + <p>An elaborate example of this logic can look something like this:</p> + +<pre> +private static final int TWO_MINUTES = 1000 * 60 * 2; + +/** Determines whether one Location reading is better than the current Location fix + * @param location The new Location that you want to evaluate + * @param currentBestLocation The current Location fix, to which you want to compare the new one + */ +protected boolean isBetterLocation(Location location, Location currentBestLocation) { + if (currentBestLocation == null) { + // A new location is always better than no location + return true; + } + + // Check whether the new location fix is newer or older + long timeDelta = location.getTime() - currentBestLocation.getTime(); + boolean isSignificantlyNewer = timeDelta > TWO_MINUTES; + boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES; + boolean isNewer = timeDelta > 0; + + // If it's been more than two minutes since the current location, use the new location + // because the user has likely moved + if (isSignificantlyNewer) { + return true; + // If the new location is more than two minutes older, it must be worse + } else if (isSignificantlyOlder) { + return false; + } + + // Check whether the new location fix is more or less accurate + int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy()); + boolean isLessAccurate = accuracyDelta > 0; + boolean isMoreAccurate = accuracyDelta < 0; + boolean isSignificantlyLessAccurate = accuracyDelta > 200; + + // Check if the old and new location are from the same provider + boolean isFromSameProvider = isSameProvider(location.getProvider(), + currentBestLocation.getProvider()); + + // Determine location quality using a combination of timeliness and accuracy + if (isMoreAccurate) { + return true; + } else if (isNewer && !isLessAccurate) { + return true; + } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { + return true; + } + return false; +} + +/** Checks whether two providers are the same */ +private boolean isSameProvider(String provider1, String provider2) { + if (provider1 == null) { + return provider2 == null; + } + return provider1.equals(provider2); +} +</pre> + + + <h3 id="Adjusting">Adjusting the model to save battery and data exchange</h3> + + <p>As you test your application, you might find that your model for providing good location and +good performance needs some adjustment. Here are some things you might change to find a good +balance between the two.</p> + + <h4>Reduce the size of the window</h4> + + <p>A smaller window in which you listen for location updates means less interaction with GPS and +network location services, thus, preserving battery life. But it also allows for fewer locations +from which to choose a best estimate.</p> + + <h4>Set the location providers to return updates less frequently</h4> + + <p>Reducing the rate at which new updates appear during the window can also improve battery +efficiency, but at the cost of accuracy. The value of the trade-off depends on how your +application is used. You can reduce the rate of updates by increasing the parameters in {@link +android.location.LocationManager#requestLocationUpdates requestLocationUpdates()} that specify the +interval time and minimum distance change.</p> + + <h4>Restrict a set of providers</h4> + + <p>Depending on the environment where your application is used or the desired level of accuracy, +you might choose to use only the Network Location Provider or only GPS, instead of both. Interacting +with only one of the services reduces battery usage at a potential cost of accuracy.</p> + + + <h2>Common application cases</h2> + + <p>There are many reasons you might want to obtain the user location in your application. Below +are a couple scenarios in which you can use the user location to enrich your application. Each +scenario also describes good practices for when you should start and stop listening for the +location, in order to get a good reading and help preserve battery life.</p> + + + <h3>Tagging user-created content with a location</h3> + + <p>You might be creating an application where user-created content is tagged with a location. +Think of users sharing their local experiences, posting a review for a restaurant, or recording some +content that can be augmented with their current location. A model of how this +interaction might happen, with respect to the location services, is visualized in figure 2.</p> + + <img src="{@docRoot}images/location/content-tagging.png" alt="" /> +<p class="img-caption"><strong>Figure 2.</strong> A timeline representing the window in which +the user location is obtained and listening stops when the user consumes the current location.</p> + + <p>This lines up with the previous model of how user location is obtained in code (figure 1). For +best location accuracy, you might choose to start listening for location updates when users begin +creating +the content or even when the application starts, then stop listening for updates when content is +ready to be posted or recorded. You might need to consider how long a typical task of creating the +content takes and judge if this duration allows for efficient collection of a location estimate.</p> + + + <h3>Helping the user decide on where to go</h3> + + <p>You might be creating an application that attempts to provide users with a set +of options about where to go. For example, you're looking to provide a list of nearby restaurants, +stores, and entertainment and the order of recommendations changes depending on the user +location.</p> + + <p>To accommodate such a flow, you might choose to:</p> + <ul> + <li>Rearrange recommendations when a new best estimate is obtained</li> + <li>Stop listening for updates if the order of recommendations has stabilized</li> + </ul> + + <p>This kind of model is visualized in figure 3.</p> + + <img src="{@docRoot}images/location/where-to-go.png" alt="" /> +<p class="img-caption"><strong>Figure 3.</strong> A timeline representing the window in which a +dynamic set of data is updated each time the user location updates.</p> + + + + +<h2 id="MockData">Providing Mock Location Data</h2> + +<p>As you develop your application, you'll certainly need to test how well your model for obtaining +user location works. This is most easily done using a real Android-powered device. If, however, you +don't have a device, you can still test your location-based features by mocking location data in +the Android emulator. There are three different ways to send your application mock location +data: using Eclipse, DDMS, or the "geo" command in the emulator console.</p> + +<p class="note"><strong>Note:</strong> Providing mock location data is injected as GPS location +data, so you must request location updates from <code>GPS_PROVIDER</code> in order for mock location +data to work.</p> + +<h3 id="MockEclipse">Using Eclipse</h3> + +<p>Select <b>Window</b> > <b>Show View</b> > <b>Other</b> > <b>Emulator Control</b>.</p> + +<p>In the Emulator Control panel, enter GPS coordinates under Location Controls as individual +lat/long coordinates, with a GPX file for route playback, or a KML file for multiple place marks. +(Be sure that you have a device selected in the Devices panel—available from <b>Window</b> +> <b>Show View</b> > <b>Other</b> > <b>Devices</b>.)</p> + + +<h3 id="MockDdms">Using DDMS</h3> + +<p>With the DDMS tool, you can simulate location data a few different ways:</p> +<ul> + <li>Manually send individual longitude/latitude coordinates to the device.</li> + <li>Use a GPX file describing a route for playback to the device.</li> + <li>Use a KML file describing individual place marks for sequenced playback to the device.</li> +</ul> + +<p>For more information on using DDMS to spoof location data, see the +<a href="{@docRoot}guide/developing/tools/ddms.html#emulator-control">Using DDMS guide</a>. + + +<h3 id="MockGeo">Using the "geo" command in the emulator console</h3> + +<p>To send mock location data from the command line:</p> + +<ol> + <li>Launch your application in the Android emulator and open a terminal/console in your SDK's +<code>/tools</code> directory.</li> + <li>Connect to the emulator console: +<pre>telnet localhost <em><console-port></em></pre></li> + <li>Send the location data:</p> + <ul><li><code>geo fix</code> to send a fixed geo-location. + <p>This command accepts a longitude and latitude in decimal degrees, and + an optional altitude in meters. For example:</p> + <pre>geo fix -121.45356 46.51119 4392</pre> + </li> + <li><code>geo nmea</code> to send an NMEA 0183 sentence. + <p>This command accepts a single NMEA sentence of type '$GPGGA' (fix data) or '$GPRMC' (transit + data). + For example:</p> + <pre>geo nmea $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62</pre> + </li> + </ul> + </li> +</ol> + +<p>For information about how to connect to the emulator console, see +<a href="{@docRoot}guide/developing/tools/emulator.html#console">Using the Emulator Console</a>.</p> diff --git a/docs/html/guide/topics/manifest/activity-element.jd b/docs/html/guide/topics/manifest/activity-element.jd index de8ca6d8950b..e030a4c48e5e 100644 --- a/docs/html/guide/topics/manifest/activity-element.jd +++ b/docs/html/guide/topics/manifest/activity-element.jd @@ -336,10 +336,10 @@ it can also be set as a raw string. </p></dd> <dt><a name="lmode"></a>{@code android:launchMode}</dt> -<dd>An instruction on how the activity should be launched. There are four modes +<dd>An instruction on how the activity should be launched. There are four modes that work in conjunction with activity flags ({@code FLAG_ACTIVITY_*} constants) -in {@link android.content.Intent} objects to determine what should happen when -the activity is called upon to handle an intent. They are: +in {@link android.content.Intent} objects to determine what should happen when +the activity is called upon to handle an intent. They are:</p> <p style="margin-left: 2em">"{@code standard}" <br>"{@code singleTop}" @@ -351,56 +351,110 @@ The default mode is "{@code standard}". </p> <p> -The modes fall into two main groups, with "{@code standard}" and -"{@code singleTop}" activities on one side, and "{@code singleTask}" and -"{@code singleInstance}" activities on the other. An activity with the -"{@code standard}" or "{@code singleTop}" launch mode can be instantiated -multiple times. The instances can belong to any task and can be located -anywhere in the activity stack. Typically, they're launched into the task -that called +As shown in the table below, the modes fall into two main groups, with +"{@code standard}" and "{@code singleTop}" activities on one side, and +"{@code singleTask}" and "{@code singleInstance}" activities on the other. +An activity with the "{@code standard}" or "{@code singleTop}" launch mode +can be instantiated multiple times. The instances can belong to any task +and can be located anywhere in the activity stack. Typically, they're +launched into the task that called <code>{@link android.content.Context#startActivity startActivity()}</code> -(unless the Intent object contains a -<code>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</code> -instruction, in which case a different task is chosen — see the +(unless the Intent object contains a +<code>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</code> +instruction, in which case a different task is chosen — see the <a href="#aff">taskAffinity</a> attribute). </p> <p> -In contrast, "{@code singleTask}" and "{@code singleInstance}" activities -can only begin a task. They are always at the root of the activity stack. -Moreover, the device can hold only one instance of the activity at a time +In contrast, "<code>singleTask</code>" and "<code>singleInstance</code>" activities +can only begin a task. They are always at the root of the activity stack. +Moreover, the device can hold only one instance of the activity at a time — only one such task. </p> <p> The "{@code standard}" and "{@code singleTop}" modes differ from each other -in just one respect: Every time there's new intent for a "{@code standard}" -activity, a new instance of the class is created to respond to that intent. +in just one respect: Every time there's a new intent for a "{@code standard}" +activity, a new instance of the class is created to respond to that intent. Each instance handles a single intent. -Similarly, a new instance of a "{@code singleTop}" activity may also be -created to handle a new intent. However, if the target task already has an -existing instance of the activity at the top of its stack, that instance -will receive the new intent (in an +Similarly, a new instance of a "{@code singleTop}" activity may also be +created to handle a new intent. However, if the target task already has an +existing instance of the activity at the top of its stack, that instance +will receive the new intent (in an <code>{@link android.app.Activity#onNewIntent onNewIntent()}</code> call); a new instance is not created. -In other circumstances — for example, if an existing instance of the -"{@code singleTop}" activity is in the target task, but not at the top of -the stack, or if it's at the top of a stack, but not in the target task +In other circumstances — for example, if an existing instance of the +"{@code singleTop}" activity is in the target task, but not at the top of +the stack, or if it's at the top of a stack, but not in the target task — a new instance would be created and pushed on the stack. -</p> +</p> <p> -The "{@code singleTask}" and "{@code singleInstance}" modes also differ from -each other in only one respect: A "{@code singleTask}" activity allows other -activities to be part of its task. It's at the root of the activity stack, -but other activities (necessarily "{@code standard}" and "{@code singleTop}" -activities) can be launched into the same task. A "{@code singleInstance}" -activity, on the other hand, permits no other activities to be part of its -task. It's the only activity in the task. If it starts another activity, -that activity is assigned to a different task — as if {@code +The "{@code singleTask}" and "{@code singleInstance}" modes also differ from +each other in only one respect: A "{@code singleTask}" activity allows other +activities to be part of its task. It's always at the root of its task, but +other activities (necessarily "{@code standard}" and "{@code singleTop}" +activities) can be launched into that task. A "{@code singleInstance}" +activity, on the other hand, permits no other activities to be part of its task. +It's the only activity in the task. If it starts another activity, that +activity is assigned to a different task — as if {@code FLAG_ACTIVITY_NEW_TASK} was in the intent. </p> +<table> +<tr> +<th>Use Cases</th> +<th>Launch Mode</th> +<th>Multiple Instances?</th> +<th>Comments</th> +</tr> +<tr> +<td rowspan="2" style="width:20%;">Normal launches for most activities</td> +<td>"<code>standard</code>"</td> +<td>Yes</td> +<td>Default. The system always creates a new instance of the activity in the +target task and routes the intent to it.</td> +</tr> +<tr> +<td>"<code>singleTop</code>"</td> +<td>Conditionally</td> +<td>If an instance of the activity already exists at the top of the target task, +the system routes the intent to that instance through a call to its {@link +android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a +new instance of the activity.</td> +</tr> +<tr> +<td rowspan="2">Specialized launches<br> +<em>(not recommended for general use)</em></td> +<td>"<code>singleTask</code>"</td> +<td>No</td> +<td>The system creates the activity at the root of a new task and routes the +intent to it. However, if an instance of the activity already exists, the system +routes the intent to existing instance through a call to its {@link +android.app.Activity#onNewIntent onNewIntent()} method, rather than creating a +new one.</td> +</tr> +<tr> +<td>"<code>singleInstance</code>"</td> +<td>No</td> +<td>Same as "<code>singleTask"</code>, except that the system doesn't launch any +other activities into the task holding the instance. The activity is always the +single and only member of its task.</td> +</tr> +</table> + +<p>As shown in the table above, <code>standard</code> is the default mode and is +appropriate for most types of activities. <code>SingleTop</code> is also a +common and useful launch mode for many types of activities. The other modes +— <code>singleTask</code> and <code>singleInstance</code> — are +<span style="color:red">not appropriate for most applications</span>, +since they result in an interaction model that is likely to be unfamiliar to +users and is very different from most other applications. + +<p>Regardless of the launch mode that you choose, make sure to test the usability +of the activity during launch and when navigating back to it from +other activities and tasks using the BACK key. </p> + <p>For more information on launch modes and their interaction with Intent flags, see the <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Activities and diff --git a/docs/html/guide/topics/providers/content-providers.jd b/docs/html/guide/topics/providers/content-providers.jd index 30f8d8c3c9c1..da4e7a1ff705 100644 --- a/docs/html/guide/topics/providers/content-providers.jd +++ b/docs/html/guide/topics/providers/content-providers.jd @@ -779,7 +779,7 @@ Use the {@link android.net.Uri Uri} methods to help determine what is being requested. Here is the general format for each type:</p></li> <ul> -<li><p>For a single record: {@code vnd.android.cursor.item/vnd.<em>yourcompanyname.contenttype</em}</p> +<li><p>For a single record: {@code vnd.android.cursor.item/vnd.<em>yourcompanyname.contenttype</em>}</p> <p>For example, a request for train record 122, like this URI,</p> <p style="margin-left: 2em">{@code content://com.example.transportationprovider/trains/122}</p> diff --git a/docs/html/guide/topics/resources/more-resources.jd b/docs/html/guide/topics/resources/more-resources.jd index a6475713eb92..6cae1eb4153c 100644 --- a/docs/html/guide/topics/resources/more-resources.jd +++ b/docs/html/guide/topics/resources/more-resources.jd @@ -216,10 +216,13 @@ is specified with a number followed by a unit of measure. For example: 10px, 2in, 5sp. The following units of measure are supported by Android:</p> <dl> <dt>{@code dp}</dt> - <dd>Density-independent Pixels - an abstract unit that is based on the physical density of the screen. - These units are relative to a 160 dpi screen, so one dp is one pixel on a 160 dpi screen. The ratio of - dp-to-pixel will change with the screen density, but not necessarily in direct proportion. The - compiler accepts both "dip" and "dp", though "dp" is more consistent with "sp".</dd> + <dd>Density-independent Pixels - an abstract unit that is based on the physical density of the +screen. These units are relative to a 160 dpi (dots per inch) screen, so <em>{@code 160dp} is +always one inch</em> regardless of the screen density. The ratio of dp-to-pixel will change with the +screen density, but not necessarily in direct proportion. You should use these units when specifying +view dimensions in your layout, so the UI properly scales to render at the same actual size on +different screens. (The compiler accepts both "dip" and "dp", though "dp" is more consistent with +"sp".)</dd> <dt>{@code sp}</dt> <dd>Scale-independent Pixels - this is like the dp unit, but it is also scaled by the user's font size preference. It is recommend you use this unit when specifying font sizes, so they will be adjusted diff --git a/docs/html/images/location/content-tagging.png b/docs/html/images/location/content-tagging.png Binary files differnew file mode 100644 index 000000000000..d58bfeeed2fe --- /dev/null +++ b/docs/html/images/location/content-tagging.png diff --git a/docs/html/images/location/getting-location.png b/docs/html/images/location/getting-location.png Binary files differnew file mode 100644 index 000000000000..a5905ecd46ab --- /dev/null +++ b/docs/html/images/location/getting-location.png diff --git a/docs/html/images/location/where-to-go.png b/docs/html/images/location/where-to-go.png Binary files differnew file mode 100644 index 000000000000..59f5983a367a --- /dev/null +++ b/docs/html/images/location/where-to-go.png diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 185d72a966e3..8349fe6fa797 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -47,7 +47,8 @@ <bool name="def_networks_available_notification_on">true</bool> <bool name="def_backup_enabled">false</bool> - <string name="def_backup_transport" translatable="false"></string> + <string name="def_backup_transport" translatable="false">android/com.android.internal.backup.LocalTransport</string> + <!-- Default value for whether or not to pulse the notification LED when there is a pending notification --> <bool name="def_notification_pulse">true</bool> diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 7bbc32ffec28..55f9d60d78cc 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -1029,11 +1029,10 @@ class PowerManagerService extends IPowerManager.Stub // If they gave a timeoutOverride it is the number of seconds // to screen-off. Figure out where in the countdown cycle we // should jump to. - private void setTimeoutLocked(long now, long timeoutOverride, int nextState) { + private void setTimeoutLocked(long now, final long originalTimeoutOverride, int nextState) { + long timeoutOverride = originalTimeoutOverride; if (mBootCompleted) { synchronized (mLocks) { - mHandler.removeCallbacks(mTimeoutTask); - mTimeoutTask.nextState = nextState; long when = 0; if (timeoutOverride <= 0) { switch (nextState) @@ -1084,6 +1083,12 @@ class PowerManagerService extends IPowerManager.Stub + " timeoutOverride=" + timeoutOverride + " nextState=" + nextState + " when=" + when); } + + mHandler.removeCallbacks(mTimeoutTask); + mTimeoutTask.nextState = nextState; + mTimeoutTask.remainingTimeoutOverride = timeoutOverride > 0 + ? (originalTimeoutOverride - timeoutOverride) + : -1; mHandler.postAtTime(mTimeoutTask, when); mNextTimeout = when; // for debugging } @@ -1099,6 +1104,7 @@ class PowerManagerService extends IPowerManager.Stub private class TimeoutTask implements Runnable { int nextState; // access should be synchronized on mLocks + long remainingTimeoutOverride; public void run() { synchronized (mLocks) { @@ -1119,11 +1125,11 @@ class PowerManagerService extends IPowerManager.Stub { case SCREEN_BRIGHT: if (mDimDelay >= 0) { - setTimeoutLocked(now, SCREEN_DIM); + setTimeoutLocked(now, remainingTimeoutOverride, SCREEN_DIM); break; } case SCREEN_DIM: - setTimeoutLocked(now, SCREEN_OFF); + setTimeoutLocked(now, remainingTimeoutOverride, SCREEN_OFF); break; } } @@ -2054,6 +2060,7 @@ class PowerManagerService extends IPowerManager.Stub + " mUserState=0x" + Integer.toHexString(mUserState) + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState) + " mProximitySensorActive=" + mProximitySensorActive + + " timeoutOverride=" + timeoutOverride + " force=" + force); } // ignore user activity if we are in the process of turning off the screen |