Merge "Keep the BugreportWarningActivity screen on. Make the bugreport warning text scrollable." into lmp-sprout-dev
diff --git a/Android.mk b/Android.mk
index 6a9da2f..b323650 100644
--- a/Android.mk
+++ b/Android.mk
@@ -187,7 +187,6 @@
core/java/android/nfc/INfcAdapterExtras.aidl \
core/java/android/nfc/INfcTag.aidl \
core/java/android/nfc/INfcCardEmulation.aidl \
- core/java/android/nfc/INfcLockscreenDispatch.aidl \
core/java/android/nfc/INfcUnlockHandler.aidl \
core/java/android/os/IBatteryPropertiesListener.aidl \
core/java/android/os/IBatteryPropertiesRegistrar.aidl \
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 68606836..1e1a613 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -80,6 +80,9 @@
private final static boolean DEBUG = false;
private final static boolean DEBUG_ICONS = false;
+ // Default flags to use with PackageManager when no flags are given.
+ private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
+
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -730,7 +733,7 @@
}
if (appInfo == null) {
try {
- appInfo = getApplicationInfo(packageName, 0);
+ appInfo = getApplicationInfo(packageName, sDefaultFlags);
} catch (NameNotFoundException e) {
return null;
}
@@ -770,7 +773,7 @@
@Override public Drawable getActivityIcon(ComponentName activityName)
throws NameNotFoundException {
- return getActivityInfo(activityName, 0).loadIcon(this);
+ return getActivityInfo(activityName, sDefaultFlags).loadIcon(this);
}
@Override public Drawable getActivityIcon(Intent intent)
@@ -799,13 +802,13 @@
@Override public Drawable getApplicationIcon(String packageName)
throws NameNotFoundException {
- return getApplicationIcon(getApplicationInfo(packageName, 0));
+ return getApplicationIcon(getApplicationInfo(packageName, sDefaultFlags));
}
@Override
public Drawable getActivityBanner(ComponentName activityName)
throws NameNotFoundException {
- return getActivityInfo(activityName, 0).loadBanner(this);
+ return getActivityInfo(activityName, sDefaultFlags).loadBanner(this);
}
@Override
@@ -832,13 +835,13 @@
@Override
public Drawable getApplicationBanner(String packageName)
throws NameNotFoundException {
- return getApplicationBanner(getApplicationInfo(packageName, 0));
+ return getApplicationBanner(getApplicationInfo(packageName, sDefaultFlags));
}
@Override
public Drawable getActivityLogo(ComponentName activityName)
throws NameNotFoundException {
- return getActivityInfo(activityName, 0).loadLogo(this);
+ return getActivityInfo(activityName, sDefaultFlags).loadLogo(this);
}
@Override
@@ -865,7 +868,7 @@
@Override
public Drawable getApplicationLogo(String packageName)
throws NameNotFoundException {
- return getApplicationLogo(getApplicationInfo(packageName, 0));
+ return getApplicationLogo(getApplicationInfo(packageName, sDefaultFlags));
}
@Override
@@ -914,7 +917,7 @@
@Override public Resources getResourcesForActivity(
ComponentName activityName) throws NameNotFoundException {
return getResourcesForApplication(
- getActivityInfo(activityName, 0).applicationInfo);
+ getActivityInfo(activityName, sDefaultFlags).applicationInfo);
}
@Override public Resources getResourcesForApplication(
@@ -926,7 +929,8 @@
Resources r = mContext.mMainThread.getTopLevelResources(
sameUid ? app.sourceDir : app.publicSourceDir,
sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
- app.resourceDirs, null, Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);
+ app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
+ null, mContext.mPackageInfo);
if (r != null) {
return r;
}
@@ -936,7 +940,7 @@
@Override public Resources getResourcesForApplication(
String appPackageName) throws NameNotFoundException {
return getResourcesForApplication(
- getApplicationInfo(appPackageName, 0));
+ getApplicationInfo(appPackageName, sDefaultFlags));
}
/** @hide */
@@ -951,7 +955,7 @@
return mContext.mMainThread.getSystemContext().getResources();
}
try {
- ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, 0, userId);
+ ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, sDefaultFlags, userId);
if (ai != null) {
return getResourcesForApplication(ai);
}
@@ -1136,7 +1140,7 @@
}
if (appInfo == null) {
try {
- appInfo = getApplicationInfo(packageName, 0);
+ appInfo = getApplicationInfo(packageName, sDefaultFlags);
} catch (NameNotFoundException e) {
return null;
}
@@ -1164,7 +1168,7 @@
ApplicationInfo appInfo) {
if (appInfo == null) {
try {
- appInfo = getApplicationInfo(packageName, 0);
+ appInfo = getApplicationInfo(packageName, sDefaultFlags);
} catch (NameNotFoundException e) {
return null;
}
diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java
index b8575d7..68ea9e9 100644
--- a/core/java/android/app/backup/WallpaperBackupHelper.java
+++ b/core/java/android/app/backup/WallpaperBackupHelper.java
@@ -39,6 +39,11 @@
private static final String TAG = "WallpaperBackupHelper";
private static final boolean DEBUG = false;
+ // If 'true', then apply an acceptable-size heuristic at restore time, dropping back
+ // to the factory default wallpaper if the restored one differs "too much" from the
+ // device's preferred wallpaper image dimensions.
+ private static final boolean REJECT_OUTSIZED_RESTORE = false;
+
// This path must match what the WallpaperManagerService uses
// TODO: Will need to change if backing up non-primary user's wallpaper
public static final String WALLPAPER_IMAGE =
@@ -130,27 +135,36 @@
if (DEBUG) Slog.d(TAG, "Restoring wallpaper image w=" + options.outWidth
+ " h=" + options.outHeight);
- // We accept any wallpaper that is at least as wide as our preference
- // (i.e. wide enough to fill the screen), and is within a comfortable
- // factor of the target height, to avoid significant clipping/scaling/
- // letterboxing.
- final double heightRatio = mDesiredMinHeight / options.outHeight;
- if (options.outWidth >= mDesiredMinWidth
- && heightRatio > 0 && heightRatio < 1.33) {
- // sufficiently close to our resolution; go ahead and use it
- Slog.d(TAG, "Applying restored wallpaper image.");
- f.renameTo(new File(WALLPAPER_IMAGE));
- // TODO: spin a service to copy the restored image to sd/usb storage,
- // since it does not exist anywhere other than the private wallpaper
- // file.
- } else {
- Slog.i(TAG, "Restored image dimensions (w="
- + options.outWidth + ", h=" + options.outHeight
- + ") too far off target (tw="
- + mDesiredMinWidth + ", th=" + mDesiredMinHeight
- + "); falling back to default wallpaper.");
- f.delete();
+ if (REJECT_OUTSIZED_RESTORE) {
+ // We accept any wallpaper that is at least as wide as our preference
+ // (i.e. wide enough to fill the screen), and is within a comfortable
+ // factor of the target height, to avoid significant clipping/scaling/
+ // letterboxing.
+ final double heightRatio = mDesiredMinHeight / options.outHeight;
+ if (options.outWidth < mDesiredMinWidth
+ || heightRatio <= 0
+ || heightRatio >= 1.33) {
+ // Not wide enough for the screen, or too short/tall to be a good fit
+ // for the height of the screen, broken image file, or the system's
+ // desires for wallpaper size are in a bad state. Probably one of the
+ // first two.
+ Slog.i(TAG, "Restored image dimensions (w="
+ + options.outWidth + ", h=" + options.outHeight
+ + ") too far off target (tw="
+ + mDesiredMinWidth + ", th=" + mDesiredMinHeight
+ + "); falling back to default wallpaper.");
+ f.delete();
+ return;
+ }
}
+
+ // We passed the acceptable-dimensions test (if any), so we're going to
+ // use the restored image.
+ // TODO: spin a service to copy the restored image to sd/usb storage,
+ // since it does not exist anywhere other than the private wallpaper
+ // file.
+ Slog.d(TAG, "Applying restored wallpaper image.");
+ f.renameTo(new File(WALLPAPER_IMAGE));
}
} else if (key.equals(WALLPAPER_INFO_KEY)) {
// XML file containing wallpaper info
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index db8fac2..f249c5f 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -880,6 +880,8 @@
/** {@hide} */
public String appLabel;
/** {@hide} */
+ public long appIconLastModified = -1;
+ /** {@hide} */
public Uri originatingUri;
/** {@hide} */
public Uri referrerUri;
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 9129121..5b926ad 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -26,7 +26,6 @@
import android.nfc.INfcAdapterExtras;
import android.nfc.INfcTag;
import android.nfc.INfcCardEmulation;
-import android.nfc.INfcLockscreenDispatch;
import android.nfc.INfcUnlockHandler;
import android.os.Bundle;
diff --git a/core/java/android/nfc/INfcLockscreenDispatch.aidl b/core/java/android/nfc/INfcLockscreenDispatch.aidl
deleted file mode 100644
index 27e9a8c..0000000
--- a/core/java/android/nfc/INfcLockscreenDispatch.aidl
+++ /dev/null
@@ -1,12 +0,0 @@
-package android.nfc;
-
-import android.nfc.Tag;
-
-/**
- * @hide
- */
-interface INfcLockscreenDispatch {
-
- boolean onTagDetected(in Tag tag);
-
-}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 37294e7..984f12f 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -721,6 +721,39 @@
}
/**
+ * Creates a secondary user with the specified name and options and configures it with default
+ * restrictions.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ *
+ * @param name the user's name
+ * @param flags flags that identify the type of user and other properties.
+ * @see UserInfo
+ *
+ * @return the UserInfo object for the created user, or null if the user could not be created.
+ * @hide
+ */
+ public UserInfo createSecondaryUser(String name, int flags) {
+ try {
+ UserInfo user = mService.createUser(name, flags);
+ if (user == null) {
+ return null;
+ }
+ Bundle userRestrictions = mService.getUserRestrictions(user.id);
+ addDefaultUserRestrictions(userRestrictions);
+ mService.setUserRestrictions(userRestrictions, user.id);
+ return user;
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not create a user", re);
+ return null;
+ }
+ }
+
+ private static void addDefaultUserRestrictions(Bundle restrictions) {
+ restrictions.putBoolean(DISALLOW_OUTGOING_CALLS, true);
+ restrictions.putBoolean(DISALLOW_SMS, true);
+ }
+
+ /**
* Creates a user with the specified name and options as a profile of another user.
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
*
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 45d790b..e9baaa8 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -1503,6 +1503,7 @@
}
}
+ @Deprecated
public static void writeBitmapAttribute(XmlSerializer out, String name, Bitmap value)
throws IOException {
if (value != null) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9e9866f..b45237b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1857,4 +1857,6 @@
state changes. Voice radio tech change will always trigger an update of
phone object irrespective of this config -->
<bool name="config_switch_phone_on_voice_reg_state_change">true</bool>
+
+ <bool name="config_sms_force_7bit_encoding">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 64699cf..82d398e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1334,6 +1334,7 @@
<java-symbol type="xml" name="audio_assets" />
<java-symbol type="xml" name="global_keys" />
<java-symbol type="xml" name="default_zen_mode_config" />
+ <java-symbol type="xml" name="sms_7bit_translation_table" />
<java-symbol type="raw" name="color_fade_vert" />
<java-symbol type="raw" name="color_fade_frag" />
@@ -2071,6 +2072,7 @@
<java-symbol type="array" name="networks_not_clear_data" />
<java-symbol type="bool" name="config_switch_phone_on_voice_reg_state_change" />
<java-symbol type="string" name="whichHomeApplicationNamed" />
+ <java-symbol type="bool" name="config_sms_force_7bit_encoding" />
<!-- From MSIM Account -->
<java-symbol type="layout" name="simple_account_item" />
diff --git a/core/res/res/xml/sms_7bit_translation_table.xml b/core/res/res/xml/sms_7bit_translation_table.xml
new file mode 100644
index 0000000..a63792d
--- /dev/null
+++ b/core/res/res/xml/sms_7bit_translation_table.xml
@@ -0,0 +1,271 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** 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.
+*/
+-->
+<SmsEnforce7BitTranslationTable>
+ <TranslationType Type="common">
+ <Character from="0x00C1" to="0x0041"/>
+ <Character from="0x00C2" to="0x0041"/>
+ <Character from="0x00C3" to="0x0041"/>
+ <Character from="0x00CA" to="0x0045"/>
+ <Character from="0x00CB" to="0x0045"/>
+ <Character from="0x00CD" to="0x0049"/>
+ <Character from="0x00CE" to="0x0049"/>
+ <Character from="0x00CF" to="0x0049"/>
+ <Character from="0x00D3" to="0x004F"/>
+ <Character from="0x00D4" to="0x004F"/>
+ <Character from="0x00D5" to="0x004F"/>
+ <Character from="0x00D7" to="0x0078"/>
+ <Character from="0x00DA" to="0x0055"/>
+ <Character from="0x00DB" to="0x0055"/>
+ <Character from="0x00E1" to="0x0061"/>
+ <Character from="0x00E2" to="0x0061"/>
+ <Character from="0x00E3" to="0x0061"/>
+ <Character from="0x00EA" to="0x0065"/>
+ <Character from="0x00EB" to="0x0065"/>
+ <Character from="0x00ED" to="0x0069"/>
+ <Character from="0x00EE" to="0x0069"/>
+ <Character from="0x00EF" to="0x0069"/>
+ <Character from="0x00F3" to="0x006F"/>
+ <Character from="0x00F4" to="0x006F"/>
+ <Character from="0x00F5" to="0x006F"/>
+ <Character from="0x00FA" to="0x0075"/>
+ <Character from="0x00FB" to="0x0075"/>
+ <Character from="0x0104" to="0x0041"/>
+ <Character from="0x0105" to="0x0061"/>
+ <Character from="0x0106" to="0x0043"/>
+ <Character from="0x0107" to="0x0063"/>
+ <Character from="0x0110" to="0x0044"/>
+ <Character from="0x0118" to="0x0045"/>
+ <Character from="0x0119" to="0x0065"/>
+ <Character from="0x011E" to="0x0047"/>
+ <Character from="0x011F" to="0x0067"/>
+ <Character from="0x0131" to="0x0069"/>
+ <Character from="0x0139" to="0x004C"/>
+ <Character from="0x013A" to="0x006C"/>
+ <Character from="0x0141" to="0x004C"/>
+ <Character from="0x0142" to="0x006C"/>
+ <Character from="0x0143" to="0x004E"/>
+ <Character from="0x0144" to="0x006E"/>
+ <Character from="0x0150" to="0x004F"/>
+ <Character from="0x0151" to="0x006F"/>
+ <Character from="0x015A" to="0x0053"/>
+ <Character from="0x015B" to="0x0073"/>
+ <Character from="0x015E" to="0x0053"/>
+ <Character from="0x015F" to="0x0073"/>
+ <Character from="0x0170" to="0x0055"/>
+ <Character from="0x0171" to="0x0075"/>
+ <Character from="0x0179" to="0x005A"/>
+ <Character from="0x017A" to="0x007A"/>
+ <Character from="0x017B" to="0x005A"/>
+ <Character from="0x017C" to="0x007A"/>
+ <Character from="0x0386" to="0x0041"/>
+ <Character from="0x0388" to="0x0045"/>
+ <Character from="0x0389" to="0x0048"/>
+ <Character from="0x038A" to="0x0049"/>
+ <Character from="0x038C" to="0x004F"/>
+ <Character from="0x038E" to="0x0059"/>
+ <Character from="0x038F" to="0x03A9"/>
+ <Character from="0x0390" to="0x0049"/>
+ <Character from="0x0391" to="0x0041"/>
+ <Character from="0x0392" to="0x0042"/>
+ <Character from="0x0395" to="0x0045"/>
+ <Character from="0x0396" to="0x005A"/>
+ <Character from="0x0397" to="0x0048"/>
+ <Character from="0x0399" to="0x0049"/>
+ <Character from="0x039A" to="0x004B"/>
+ <Character from="0x039C" to="0x004D"/>
+ <Character from="0x039D" to="0x004E"/>
+ <Character from="0x039F" to="0x004F"/>
+ <Character from="0x03A1" to="0x0050"/>
+ <Character from="0x03A4" to="0x0054"/>
+ <Character from="0x03A5" to="0x0059"/>
+ <Character from="0x03A7" to="0x0058"/>
+ <Character from="0x03AA" to="0x0049"/>
+ <Character from="0x03AB" to="0x0059"/>
+ <Character from="0x03AC" to="0x0041"/>
+ <Character from="0x03AD" to="0x0045"/>
+ <Character from="0x03AE" to="0x0048"/>
+ <Character from="0x03AF" to="0x0049"/>
+ <Character from="0x03B0" to="0x0059"/>
+ <Character from="0x03B1" to="0x0041"/>
+ <Character from="0x03B2" to="0x0042"/>
+ <Character from="0x03B3" to="0x0393"/>
+ <Character from="0x03B4" to="0x0394"/>
+ <Character from="0x03B5" to="0x0045"/>
+ <Character from="0x03B6" to="0x005A"/>
+ <Character from="0x03B7" to="0x0048"/>
+ <Character from="0x03B8" to="0x0398"/>
+ <Character from="0x03B9" to="0x0049"/>
+ <Character from="0x03BA" to="0x004B"/>
+ <Character from="0x03BB" to="0x039B"/>
+ <Character from="0x03BC" to="0x004D"/>
+ <Character from="0x03BD" to="0x004E"/>
+ <Character from="0x03BE" to="0x039E"/>
+ <Character from="0x03BF" to="0x004F"/>
+ <Character from="0x03C0" to="0x03A0"/>
+ <Character from="0x03C1" to="0x0050"/>
+ <Character from="0x03C2" to="0x03A3"/>
+ <Character from="0x03C3" to="0x03A3"/>
+ <Character from="0x03C4" to="0x0054"/>
+ <Character from="0x03C5" to="0x0059"/>
+ <Character from="0x03C6" to="0x03A6"/>
+ <Character from="0x03C7" to="0x0058"/>
+ <Character from="0x03C8" to="0x03A8"/>
+ <Character from="0x03C9" to="0x03A9"/>
+ <Character from="0x03CA" to="0x0049"/>
+ <Character from="0x03CB" to="0x0059"/>
+ <Character from="0x03CC" to="0x004F"/>
+ <Character from="0x03CD" to="0x0059"/>
+ <Character from="0x03CE" to="0x03A9"/>
+ <Character from="0x2010" to="0x002D"/>
+ <Character from="0x00D0" to="0x0044"/>
+ <Character from="0x00DD" to="0x0059"/>
+ <Character from="0x00FD" to="0x0079"/>
+ <Character from="0x00FF" to="0x0079"/>
+ <Character from="0x0100" to="0x0041"/>
+ <Character from="0x0101" to="0x0061"/>
+ <Character from="0x010C" to="0x0043"/>
+ <Character from="0x010D" to="0x0063"/>
+ <Character from="0x010E" to="0x0044"/>
+ <Character from="0x010F" to="0x0064"/>
+ <Character from="0x0111" to="0x0064"/>
+ <Character from="0x0112" to="0x0045"/>
+ <Character from="0x0113" to="0x0065"/>
+ <Character from="0x011A" to="0x0045"/>
+ <Character from="0x011B" to="0x0065"/>
+ <Character from="0x012A" to="0x0049"/>
+ <Character from="0x012B" to="0x0069"/>
+ <Character from="0x0132" to="0x004A"/>
+ <Character from="0x0133" to="0x006A"/>
+ <Character from="0x013D" to="0x004C"/>
+ <Character from="0x013E" to="0x006C"/>
+ <Character from="0x0147" to="0x004E"/>
+ <Character from="0x0148" to="0x006E"/>
+ <Character from="0x014C" to="0x004F"/>
+ <Character from="0x014D" to="0x006F"/>
+ <Character from="0x0152" to="0x004F"/>
+ <Character from="0x0153" to="0x006F"/>
+ <Character from="0x0154" to="0x0052"/>
+ <Character from="0x0155" to="0x0072"/>
+ <Character from="0x0158" to="0x0052"/>
+ <Character from="0x0159" to="0x0072"/>
+ <Character from="0x0160" to="0x0053"/>
+ <Character from="0x0161" to="0x0073"/>
+ <Character from="0x0164" to="0x0054"/>
+ <Character from="0x0165" to="0x0074"/>
+ <Character from="0x016A" to="0x0055"/>
+ <Character from="0x016B" to="0x0075"/>
+ <Character from="0x016E" to="0x0055"/>
+ <Character from="0x016F" to="0x0075"/>
+ <Character from="0x0178" to="0x0079"/>
+ <Character from="0x017D" to="0x005A"/>
+ <Character from="0x017E" to="0x007A"/>
+ <Character from="0x0060" to="0x0027"/>
+ <Character from="0x00A9" to="0x0063"/>
+ <Character from="0x00AB" to="0x003C"/>
+ <Character from="0x00AE" to="0x0052"/>
+ <Character from="0x00AF" to="0x002D"/>
+ <Character from="0x00B0" to="0x006F"/>
+ <Character from="0x00BB" to="0x003E"/>
+ <Character from="0x02DD" to="0x0022"/>
+ <Character from="0x2013" to="0x002D"/>
+ <Character from="0x2014" to="0x002D"/>
+ <Character from="0x201A" to="0x0027"/>
+ <Character from="0x201C" to="0x0022"/>
+ <Character from="0x201D" to="0x0022"/>
+ <Character from="0x201E" to="0x0022"/>
+ <Character from="0x2020" to="0x002B"/>
+ <Character from="0x2021" to="0x002B"/>
+ <Character from="0x2022" to="0x002E"/>
+ <Character from="0x2026" to="0x002E"/>
+ <Character from="0x2030" to="0x0025"/>
+ <Character from="0x2039" to="0x003C"/>
+ <Character from="0x203A" to="0x003E"/>
+ <Character from="0x20A3" to="0x0023"/>
+ <Character from="0x20A4" to="0x0023"/>
+ <Character from="0x20B1" to="0x0023"/>
+ <Character from="0x2264" to="0x003C"/>
+ <Character from="0x2265" to="0x003E"/>
+ <Character from="0x0102" to="0x0041"/>
+ <Character from="0x0103" to="0x0061"/>
+ <Character from="0x0162" to="0x0054"/>
+ <Character from="0x0163" to="0x0074"/>
+ </TranslationType>
+ <TranslationType Type="gsm">
+ <Character from="0x00C0" to="0x00E0"/>
+ <Character from="0x00C8" to="0x00E8"/>
+ <Character from="0x00CC" to="0x00EC"/>
+ <Character from="0x00D2" to="0x00F2"/>
+ <Character from="0x00D9" to="0x00F9"/>
+ <Character from="0x00E7" to="0x00C7"/>
+ <Character from="0x00A2" to="0x003F"/>
+ <Character from="0x00A6" to="0x003F"/>
+ <Character from="0x00B1" to="0x003F"/>
+ <Character from="0x00B6" to="0x003F"/>
+ <Character from="0x00F7" to="0x003F"/>
+ <Character from="0x0192" to="0x003F"/>
+ <Character from="0x2122" to="0x003F"/>
+ <Character from="0x221A" to="0x003F"/>
+ <Character from="0x221E" to="0x003F"/>
+ <Character from="0x2248" to="0x003F"/>
+ <Character from="0x2260" to="0x003F"/>
+ </TranslationType>
+ <TranslationType Type="cdma">
+ <Character from="0x00A1" to="0x0021"/>
+ <Character from="0x00BF" to="0x0020"/>
+ <Character from="0x00C0" to="0x0041"/>
+ <Character from="0x00C4" to="0x0041"/>
+ <Character from="0x00C5" to="0x0041"/>
+ <Character from="0x00C6" to="0x0020"/>
+ <Character from="0x00C7" to="0x0043"/>
+ <Character from="0x00C8" to="0x0045"/>
+ <Character from="0x00C9" to="0x0045"/>
+ <Character from="0x00CC" to="0x0049"/>
+ <Character from="0x00D1" to="0x004E"/>
+ <Character from="0x00D2" to="0x004F"/>
+ <Character from="0x00D6" to="0x004F"/>
+ <Character from="0x00D8" to="0x0020"/>
+ <Character from="0x00D9" to="0x0055"/>
+ <Character from="0x00DC" to="0x0055"/>
+ <Character from="0x00DD" to="0x0059"/>
+ <Character from="0x00DF" to="0x0020"/>
+ <Character from="0x00E0" to="0x0061"/>
+ <Character from="0x00E4" to="0x0061"/>
+ <Character from="0x00E5" to="0x0061"/>
+ <Character from="0x00E6" to="0x0020"/>
+ <Character from="0x00E7" to="0x0063"/>
+ <Character from="0x00E8" to="0x0065"/>
+ <Character from="0x00E9" to="0x0065"/>
+ <Character from="0x00EC" to="0x0069"/>
+ <Character from="0x00F1" to="0x006F"/>
+ <Character from="0x00F2" to="0x006F"/>
+ <Character from="0x00F6" to="0x006F"/>
+ <Character from="0x00F8" to="0x0020"/>
+ <Character from="0x00F9" to="0x0075"/>
+ <Character from="0x00FC" to="0x0075"/>
+ <Character from="0x00A2" to="0x0023"/>
+ <Character from="0x00A6" to="0x0020"/>
+ <Character from="0x00B1" to="0x0020"/>
+ <Character from="0x00B6" to="0x0020"/>
+ <Character from="0x00F7" to="0x0020"/>
+ <Character from="0x0192" to="0x0020"/>
+ <Character from="0x2122" to="0x0020"/>
+ <Character from="0x221A" to="0x0020"/>
+ <Character from="0x221E" to="0x0020"/>
+ <Character from="0x2248" to="0x0020"/>
+ <Character from="0x2260" to="0x0020"/>
+ </TranslationType>
+</SmsEnforce7BitTranslationTable>
\ No newline at end of file
diff --git a/docs/html/sdk/download.jd b/docs/html/sdk/download.jd
index 4329102..52432cf 100644
--- a/docs/html/sdk/download.jd
+++ b/docs/html/sdk/download.jd
@@ -14,9 +14,9 @@
if (location.indexOf('?v=') != -1) {
var filename = location.substring(location.indexOf('=')+1,location.length);
if (document.getElementById('checkbox').checked) {
- document.location = "http://dl.google.com/android/" + filename;
+ document.location = "https://dl.google.com/android/" + filename;
}
- document.getElementById('click-download').setAttribute("href", "http://dl.google.com/android/"
+ document.getElementById('click-download').setAttribute("href", "https://dl.google.com/android/"
+ filename);
$("#terms-form").hide(500);
$("#next-steps").show(500);
diff --git a/docs/html/tools/sdk/eclipse-adt.jd b/docs/html/tools/sdk/eclipse-adt.jd
index 469d11f..deafed5 100644
--- a/docs/html/tools/sdk/eclipse-adt.jd
+++ b/docs/html/tools/sdk/eclipse-adt.jd
@@ -70,8 +70,8 @@
<li>Java 1.6 or higher is required if you are targeting other releases.</li>
<li>Eclipse Indigo (Version 3.7.2) or higher is required.</li>
<li>This version of ADT is designed for use with
- <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r23.0.4</a>.
- If you haven't already installed SDK Tools r23.0.4 into your SDK, use the
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r23.0.2</a>.
+ If you haven't already installed SDK Tools r23.0.2 into your SDK, use the
Android SDK Manager to do so.</li>
</ul>
</dd>
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 4dd57ab..5443c56 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -784,6 +784,34 @@
<li class="nav-section">
<div class="nav-section-header">
+ <a href="<?cs var:toroot ?>training/wearables/ui/index.html"
+ description="How to create custom user interfaces for wearable apps."
+ >Creating Custom UIs</a>
+ </div>
+ <ul>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/ui/layouts.html">Defining Layouts</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/ui/cards.html">Creating Cards</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/ui/lists.html">Creating Lists</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/ui/2d-picker.html">Creating a 2D Picker</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/ui/confirm.html">Showing Confirmations</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/ui/exit.html">Exiting Full-Screen Activities</a>
+ </li>
+ </ul>
+ </li>
+
+ <li class="nav-section">
+ <div class="nav-section-header">
<a href="<?cs var:toroot ?>training/wearables/data-layer/index.html"
description="How to sync data between handhelds and wearables."
>Sending and Syncing Data</a>
diff --git a/docs/html/training/wearables/ui/2d-picker.jd b/docs/html/training/wearables/ui/2d-picker.jd
new file mode 100644
index 0000000..8f4d8af
--- /dev/null
+++ b/docs/html/training/wearables/ui/2d-picker.jd
@@ -0,0 +1,181 @@
+page.title=Creating a 2D Picker
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#add-page-grid">Add a Page Grid</a></li>
+ <li><a href="#implement-adapter">Implement a Page Adapter</a></li>
+</ol>
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+</ul>
+</div>
+</div>
+
+<p>The <a href="{@docRoot}design/wear/structure.html#2DPicker">2D Picker</a> pattern in Android
+Wear allows users to navigate and choose from a set of items shown as pages. The Wearable UI
+Library lets you easily implement this pattern using a page grid, which is a layout manager
+that allows users to scroll vertically and horizontally through pages of data.</p>
+
+<p>To implement this pattern, you add a <code>GridViewPager</code> element to the layout
+of your activity and implement an adapter that provides a set of pages by extending
+the <code>FragmentGridPagerAdapter</code> class.</p>
+
+<p class="note"><strong>Note:</strong> The <em>GridViewPager</em> sample in the Android SDK
+demonstrates how to use the <code>GridViewPager</code> layout in your apps. This sample is
+located in the <code>android-sdk/samples/android-20/wearable/GridViewPager</code> directory.</p>
+
+
+<h2 id="add-page-grid">Add a Page Grid</h2>
+
+<p>Add a <code>GridViewPager</code> element to your layout definition as follows:</p>
+
+<pre>
+<android.support.wearable.view.GridViewPager
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</pre>
+
+<p>You can use any of the techniques described in
+<a href="{@docRoot}training/wearables/ui/layouts.html">Defining Layouts</a> to ensure that
+your 2D picker works on both round and square devices.</p>
+
+
+<h2 id="implement-adapter">Implement a Page Adapter</h2>
+
+<p>A page adapter provides a set of pages to populate a <code>GridViewPager</code> component. To
+implement this adapter, you extend the <code>FragmentGridPageAdapter</code> class from the
+Wearable UI Library</p>
+
+<p>For example, the <em>GridViewPager</em> sample in the Android SDK contains
+the following adapter implementation that provides a set of static cards with custom background
+images:</p>
+
+<pre>
+public class SampleGridPagerAdapter extends FragmentGridPagerAdapter {
+
+ private final Context mContext;
+
+ public SampleGridPagerAdapter(Context ctx, FragmentManager fm) {
+ super(fm);
+ mContext = ctx;
+ }
+
+ static final int[] BG_IMAGES = new int[] {
+ R.drawable.debug_background_1, ...
+ R.drawable.debug_background_5
+ };
+
+ // A simple container for static data in each page
+ private static class Page {
+ // static resources
+ int titleRes;
+ int textRes;
+ int iconRes;
+ ...
+ }
+
+ // Create a static set of pages in a 2D array
+ private final Page[][] PAGES = { ... };
+
+ // Override methods in FragmentGridPagerAdapter
+ ...
+}
+</pre>
+
+<p>The picker calls <code>getFragment</code> and <code>getBackground</code> to retrieve the content
+to display at each position of the grid:</p>
+
+<pre>
+// Obtain the UI fragment at the specified position
+@Override
+public Fragment getFragment(int row, int col) {
+ Page page = PAGES[row][col];
+ String title =
+ page.titleRes != 0 ? mContext.getString(page.titleRes) : null;
+ String text =
+ page.textRes != 0 ? mContext.getString(page.textRes) : null;
+ CardFragment fragment = CardFragment.create(title, text, page.iconRes);
+
+ // Advanced settings (card gravity, card expansion/scrolling)
+ fragment.setCardGravity(page.cardGravity);
+ fragment.setExpansionEnabled(page.expansionEnabled);
+ fragment.setExpansionDirection(page.expansionDirection);
+ fragment.setExpansionFactor(page.expansionFactor);
+ return fragment;
+}
+
+// Obtain the background image for the page at the specified position
+@Override
+public ImageReference getBackground(int row, int column) {
+ return ImageReference.forDrawable(BG_IMAGES[row % BG_IMAGES.length]);
+}
+</pre>
+
+<p>The <code>getRowCount</code> method tells the picker how many rows of content are
+available, and the <code>getColumnCount</code> method tells the picker how many columns
+of content are available for each of the rows.</p>
+
+<pre>
+// Obtain the number of pages (vertical)
+@Override
+public int getRowCount() {
+ return PAGES.length;
+}
+
+// Obtain the number of pages (horizontal)
+@Override
+public int getColumnCount(int rowNum) {
+ return PAGES[rowNum].length;
+}
+</pre>
+
+<p>The adapter implementation details depend on your particular set of pages. Each page provided
+by the adapter is of type <code>Fragment</code>. In this example, each page is a
+<code>CardFragment</code> instance that uses one of the default card layouts. However, you can
+combine different types of pages in the same 2D picker, such as cards, action icons, and custom
+layouts depending on your use cases.</p>
+
+<div style="float:right;margin-left:25px;width:250px">
+<img src="{@docRoot}wear/images/07_uilib.png" width="250" height="250" alt=""/>
+<p class="img-caption" style="text-align:center"><strong>Figure 1:</strong>
+The <em>GridViewPager</em> sample.</p>
+</div>
+
+<p>Not all rows need to have the same number of pages. Notice that in this example the number of
+colums is different for each row. You can also use a <code>GridViewPager</code> component to
+implement a 1D picker with only one row or only one column.</p>
+
+<p><code>GridViewPager</code> provides support for scrolling in cards whose content does not fit
+the device screen. This example configures each card to expand as required, so users can scroll
+through the card's content. When users reach the end of a scrollable card, a swipe in the same
+direction shows the next page on the grid, if one is available.</p>
+
+<p>You can specify a custom background for each page with the <code>getBackground()</code> method.
+When users swipe to navigate across pages, <code>GridViewPager</code> applies parallax
+and crossfade effects between different backgrounds automatically.</p>
+
+<h3>Assign an adapter instance to the page grid</h3>
+
+<p>In your activity, assign an instance of your adapter implementation to the
+<code>GridViewPager</code> component:</p>
+
+<pre>
+public class MainActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ ...
+ final GridViewPager pager = (GridViewPager) findViewById(R.id.pager);
+ pager.setAdapter(new SampleGridPagerAdapter(this, getFragmentManager()));
+ }
+}
+</pre>
diff --git a/docs/html/training/wearables/ui/cards.jd b/docs/html/training/wearables/ui/cards.jd
new file mode 100644
index 0000000..0633720
--- /dev/null
+++ b/docs/html/training/wearables/ui/cards.jd
@@ -0,0 +1,169 @@
+page.title=Creating Cards
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#card-fragment">Create a Card Fragment</a></li>
+ <li><a href="#card-layout">Add a Card to Your Layout</a></li>
+</ol>
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+</ul>
+</div>
+</div>
+
+
+<p>Cards present information to users with a consistent look and feel across different apps.
+This lesson shows you how to create cards in your Android Wear apps.</p>
+
+<p>The Wearable UI Library provides implementations of cards specifically designed for wearable
+devices. This library contains the <code>CardFrame</code> class, which wraps views inside
+a card-styled frame with a white background, rounded corners, and a light-drop shadow.
+<code>CardFrame</code> can only contain one direct child, usually a layout manager, to which
+you can add other views to customize the content inside the card.</p>
+
+<p>You can add cards to your app in two ways:</p>
+
+<ul>
+ <li>Use or extend the <code>CardFragment</code> class.</li>
+ <li>Add a card inside a <code>CardScrollView</code> in your layout.</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> This lesson shows you how to add cards to Android Wear
+activities. Android notifications on wearable devices are also displayed as cards. For more
+information, see <a href="{@docRoot}training/wearables/notifications/index.html">Adding Wearable
+Features to Notifications</a>.</p>
+
+
+<h2 id="card-fragment">Create a Card Fragment</h2>
+
+<p>The <code>CardFragment</code> class provides a default card layout with a title, a
+description, and an icon. Use this approach to add cards to your app if the default card layout
+shown in figure 1 meets your needs.</p>
+
+<img src="{@docRoot}wear/images/05_uilib.png" width="500" height="245" alt=""/>
+<p class="img-caption"><strong>Figure 1.</strong> The default <code>CardFragment</code> layout.</p>
+
+<p>To add a <code>CardFragment</code> to your app:</p>
+
+<ol>
+<li>In your layout, assign an ID to the element that contains the card</li>
+<li>Create a <code>CardFragment</code> instance in your activity</li>
+<li>Use the fragment manager to add the <code>CardFragment</code> instance to its container</li>
+</ol>
+
+<p>The following sample code shows the code for the screen display shown in Figure 1:</p>
+
+<pre>
+<android.support.wearable.view.BoxInsetLayout
+xmlns:android="http://schemas.android.com/apk/res/android"
+xmlns:app="http://schemas.android.com/apk/res-auto"
+android:background="@drawable/robot_background"
+android:layout_height="match_parent"
+android:layout_width="match_parent">
+
+ <FrameLayout
+ <strong>android:id="@+id/frame_layout"</strong>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_box="bottom">
+
+ </FrameLayout>
+</android.support.wearable.view.BoxInsetLayout>
+</pre>
+
+<p>The following code adds the <code>CardFragment</code> instance to the activity in Figure 1:</p>
+
+<pre>
+protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_wear_activity2);
+
+ FragmentManager fragmentManager = getFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+ CardFragment cardFragment = CardFragment.create(getString(R.string.cftitle),
+ getString(R.string.cfdesc),
+ R.drawable.p);
+ fragmentTransaction.add(R.id.frame_layout, cardFragment);
+ fragmentTransaction.commit();
+}
+</pre>
+
+<p>To create a card with a custom layout using <code>CardFragment</code>, extend this class
+and override its <code>onCreateContentView</code> method.</p>
+
+
+<h2 id="card-layout">Add a CardFrame to Your Layout</h2>
+
+<p>You can also add a card directly to your layout definition, as shown in figure 2. Use this
+approach when you want to define a custom layout for the card inside a layout definition file.</p>
+
+<img src="{@docRoot}wear/images/04_uilib.png" width="500" height="248" alt=""/>
+<p class="img-caption"><strong>Figure 2.</strong> Adding a <code>CardFrame</code> to your
+layout.</p>
+
+<p>The following layout code sample demonstrates a vertical linear layout with two elements. You
+can create more complex layouts to fit the needs of your app.</p>
+
+<pre>
+<android.support.wearable.view.BoxInsetLayout
+xmlns:android="http://schemas.android.com/apk/res/android"
+xmlns:app="http://schemas.android.com/apk/res-auto"
+android:background="@drawable/robot_background"
+android:layout_height="match_parent"
+android:layout_width="match_parent">
+
+ <<strong>android.support.wearable.view.CardScrollView</strong>
+ android:id="@+id/card_scroll_view"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ app:layout_box="bottom">
+
+ <<strong>android.support.wearable.view.CardFrame</strong>
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent">
+
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:paddingLeft="5dp">
+ <TextView
+ android:fontFamily="sans-serif-light"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@string/custom_card"
+ android:textColor="@color/black"
+ android:textSize="20sp"/>
+ <TextView
+ android:fontFamily="sans-serif-light"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@string/description"
+ android:textColor="@color/black"
+ android:textSize="14sp"/>
+ </LinearLayout>
+ </android.support.wearable.view.CardFrame>
+ </android.support.wearable.view.CardScrollView>
+</android.support.wearable.view.BoxInsetLayout>
+</pre>
+
+<p>The <code>CardScrollView</code> element in the example layout above lets you assign gravity to
+the card when its content is smaller than the container. This example aligns the card to the
+bottom of the screen:</p>
+
+<pre>
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_wear_activity2);
+
+ CardScrollView cardScrollView =
+ (CardScrollView) findViewById(R.id.card_scroll_view);
+ cardScrollView.setCardGravity(Gravity.BOTTOM);
+}
+</pre>
diff --git a/docs/html/training/wearables/ui/confirm.jd b/docs/html/training/wearables/ui/confirm.jd
new file mode 100644
index 0000000..36330a6
--- /dev/null
+++ b/docs/html/training/wearables/ui/confirm.jd
@@ -0,0 +1,166 @@
+page.title=Showing Confirmations
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#confirmation-timers">Use Automatic Confirmation Timers</a></li>
+ <li><a href="#show-confirmation">Show Confirmation Animations</a></li>
+</ol>
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+</ul>
+</div>
+</div>
+
+
+<p><a href="{@docRoot}design/wear/patterns.html#Countdown">Confirmations</a> in Android Wear apps
+use the whole screen or a larger portion of it than those in handheld apps. This ensures that
+users can see these confirmations by just glancing at the screen and that they have large enough
+touch targets to cancel an action.</p>
+
+<p>The Wearable UI Library helps you show confirmation animations and timers in your
+Android Wear apps:</p>
+
+<dl>
+<dt><em>Confirmation timers</em></dt>
+ <dd>Automatic confirmation timers show users an animated timer that lets them cancel an action
+ they just performed.</dd>
+<dt><em>Confirmation animations</em></dt>
+ <dd>Confirmation animations give users visual feedback when they complete an action.</dd>
+</dl>
+
+<p>The following sections show you how to implement these patterns.</p>
+
+
+<h2 id="confirmation-timers">Use Automatic Confirmation Timers</h2>
+
+<div style="float:right;margin-left:25px;width:230px;margin-top:10px">
+<img src="{@docRoot}wear/images/09_uilib.png" width="230" height="230" alt=""/>
+<p class="img-caption" style="text-align:center"><strong>Figure 1:</strong>
+A confirmation timer.</p>
+</div>
+
+<p>Automatic confirmation timers let users cancel an action they just performed. When the user
+performs the action, your app shows a button to cancel the action with a timer animation and
+starts the timer. The user has the option to cancel the action until the timer finishes. Your app
+gets notified if the user cancels the action and when the timer expires.</p>
+
+<p>To show a confirmation timer when users complete an action in your app:</p>
+
+<ol>
+<li>Add a <code>DelayedConfirmationView</code> element to your layout.</li>
+<li>Implement the <code>DelayedConfirmationListener</code> interface in your activity.</li>
+<li>Set the duration of the timer and start it when the user completes an action.</li>
+</ol>
+
+<p>Add the <code>DelayedConfirmationView</code> element to your layout as follows:</p>
+
+<pre>
+<android.support.wearable.view.DelayedConfirmationView
+ android:id="@+id/delayed_confirm"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:src="@drawable/cancel_circle"
+ app:circle_border_color="@color/lightblue"
+ app:circle_border_width="4dp"
+ app:circle_radius="16dp">
+</android.support.wearable.view.DelayedConfirmationView>
+</pre>
+
+<p>You can assign a drawable resource to display inside the circle with the
+<code>android:src</code> attribute and configure the parameters of the circle directly on the
+layout definition.</p>
+
+<p>To be notified when the timer finishes or when users tap on it, implement the corresponding
+listener methods in your activity:</p>
+
+<pre>
+public class WearActivity extends Activity implements
+ DelayedConfirmationView.DelayedConfirmationListener {
+
+ private DelayedConfirmationView mDelayedView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_wear_activity);
+
+ mDelayedView =
+ (DelayedConfirmationView) findViewById(R.id.delayed_confirm);
+ mDelayedView.setListener(this);
+ }
+
+ @Override
+ public void onTimerFinished(View view) {
+ // User didn't cancel, perform the action
+ }
+
+ @Override
+ public void onTimerSelected(View view) {
+ // User canceled, abort the action
+ }
+}
+</pre>
+
+<p>To start the timer, add the following code to the point in your activity where users select
+an action:</p>
+
+<pre>
+// Two seconds to cancel the action
+mDelayedView.setTotalTimeMs(2000);
+// Start the timer
+mDelayedView.start();
+</pre>
+
+
+<h2 id="show-confirmation">Show Confirmation Animations</h2>
+
+<div style="float:right;margin-left:25px;width:200px">
+<img src="{@docRoot}wear/images/08_uilib.png" width="200" height="200" alt=""/>
+<p class="img-caption" style="text-align:center"><strong>Figure 2:</strong>
+A confirmation animation.</p>
+</div>
+
+<p>To show a confirmation animation when users complete an action in your app, create an intent
+that starts <code>ConfirmationActivity</code> from one of your activities. You can specify
+one of the these animations with the <code>EXTRA_ANIMATION_TYPE</code> intent extra:</p>
+
+<ul>
+<li><code>SUCCESS_ANIMATION</code></li>
+<li><code>FAILURE_ANIMATION</code></li>
+<li><code>OPEN_ON_PHONE_ANIMATION</code></li>
+</ul>
+
+<p>You can also add a message that appears under the confirmation icon.</p>
+
+<p>To use the <code>ConfirmationActivity</code> in your app, first declare this activity in your
+manifest file:</p>
+
+<pre>
+<manifest>
+ <application>
+ ...
+ <activity
+ android:name="android.support.wearable.activity.ConfirmationActivity">
+ </activity>
+ </application>
+</manifest>
+</pre>
+
+<p>Then determine the result of the user action and start the activity with an intent:</p>
+
+<pre>
+Intent intent = new Intent(this, ConfirmationActivity.class);
+intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE,
+ ConfirmationActivity.SUCCESS_ANIMATION);
+intent.putExtra(ConfirmationActivity.EXTRA_MESSAGE,
+ getString(R.string.msg_sent));
+startActivity(intent);
+</pre>
+
+<p>After showing the confirmation animation, <code>ConfirmationActivity</code> finishes and your
+activity resumes.</p>
diff --git a/docs/html/training/wearables/ui/exit.jd b/docs/html/training/wearables/ui/exit.jd
new file mode 100644
index 0000000..b89711a
--- /dev/null
+++ b/docs/html/training/wearables/ui/exit.jd
@@ -0,0 +1,110 @@
+page.title=Exiting Full-Screen Activities
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#disable-swipe">Disable the Swipe-To-Dismiss Gesture</a></li>
+ <li><a href="#long-press">Implement the Long Press to Dismiss Pattern</a></li>
+</ol>
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+</ul>
+</div>
+</div>
+
+
+<p>By default, users exit Android Wear activities by swiping from left to right. If the app
+contains horizontally scrollable content, users first have to navigate to the edge of the
+content and then swipe again from left to right to exit the app.</p>
+
+<p>For more immersive experiences, like an app that can scroll a map in any direction, you can
+disable the swipe to exit gesture in your app. However, if you disable it, you must implement
+the long-press-to-dismiss UI pattern to let users exit your app using the
+<code>DismissOverlayView</code> class from the Wearable UI Library.
+You must also inform your users the first time they run your app that they can exit using
+a long press.</p>
+
+<p>For design guidelines on exiting Android Wear activities, see
+<a href="{@docRoot}design/wear/structure.html#Custom">Breaking out of the card</a>.</p>
+
+
+<h2 id="disable-swipe">Disable the Swipe-To-Dismiss Gesture</h2>
+
+<p>If the user interaction model of your app interferes with the swipe-to-dismiss gesture,
+you can disable it for your app. To disable the swipe-to-dismiss gesture in your app, extend
+the default theme and set the <code>android:windowSwipeToDismiss</code> attribute to
+<code>false</code>:</p>
+
+<pre>
+<style name="AppTheme" parent="Theme.DeviceDefault">
+ <item name="android:windowSwipeToDismiss">false</item>
+</style>
+</pre>
+
+<p>If you disable this gesture, you must implement the long-press-to-dismiss UI pattern to let users
+exit your app, as described in the next section.</p>
+
+
+<h2 id="long-press">Implement the Long Press to Dismiss Pattern</h2>
+
+<p>To use the <code>DissmissOverlayView</code> class in your activity, add this element to
+your layout definition such that it covers the whole screen and is placed above all other views.
+For example:</p>
+
+<pre>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <!-- other views go here -->
+
+ <android.support.wearable.view.DismissOverlayView
+ android:id="@+id/dismiss_overlay"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"/>
+<FrameLayout>
+</pre>
+
+<p>In your activity, obtain the <code>DismissOverlayView</code> element and set some introductory
+text. This text is shown to users the first time they run your app to inform them that they
+can exit the app using a long press gesture. Then use a <code>GestureDetector</code> to detect
+a long press:</p>
+
+<pre>
+public class WearActivity extends Activity {
+
+ private DismissOverlayView mDismissOverlay;
+ private GestureDetector mDetector;
+
+ public void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+ setContentView(R.layout.wear_activity);
+
+ // Obtain the DismissOverlayView element
+ mDismissOverlay = (DismissOverlayView) findViewById(R.id.dismiss_overlay);
+ mDismissOverlay.setIntroText(R.string.long_press_intro);
+ mDismissOverlay.showIntroIfNecessary();
+
+ // Configure a gesture detector
+ mDetector = new GestureDetector(this, new SimpleOnGestureListener() {
+ public void onLongPress(MotionEvent ev) {
+ mDismissOverlay.show();
+ }
+ });
+ }
+
+ // Capture long presses
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return mDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
+ }
+}
+</pre>
+
+<p>When the system detects a long press gesture, <code>DismissOverlayView</code> shows an
+<strong>Exit</strong> button, which terminates your activity if the user presses it.</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/ui/index.jd b/docs/html/training/wearables/ui/index.jd
new file mode 100644
index 0000000..8ef6fe7
--- /dev/null
+++ b/docs/html/training/wearables/ui/index.jd
@@ -0,0 +1,57 @@
+page.title=Creating Custom UIs for Wear Devices
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+ <h2>Dependencies and Prerequisites</h2>
+ <ul>
+ <li>Android Studio 0.8 or later and Gradle 0.12 or later</li>
+ </ul>
+</div>
+</div>
+
+<p>User interfaces for wearable apps differ significantly from those built for handheld devices.
+Apps for wearables should follow the Android Wear <a href="{@docRoot}design/wear/index.html">design
+principles</a> and implement the recommended <a href="{@docRoot}design/wear/patterns.html">UI
+patterns</a>, which ensure a consistent user experience across apps that is optimized for
+wearables.</a>
+
+<p>This class teaches you how to create custom UIs for your
+<a href="{@docRoot}training/wearables/apps/creating.html">wearable apps</a> and
+<a href="{@docRoot}training/wearables/apps/layouts.html#CustomNotifications">custom
+notifications</a> that look good on any Android Wear device by implementing these
+UI patterns:</p>
+
+<ul>
+<li>Cards</li>
+<li>Countdowns and confirmations</li>
+<li>Long press to dismiss</li>
+<li>2D Pickers</li>
+<li>Selection lists</li>
+</ul>
+
+<p>The Wearable UI Library, which is part of the Google Repository in the Android SDK,
+provides classes that help you implement these patterns and create layouts that work on
+both round and square Android Wear devices.</p>
+
+<p class="note"><b>Note:</b> We recommend using Android Studio for Android Wear development
+as it provides project setup, library inclusion, and packaging conveniences that aren't available
+in ADT. This training assumes you are using Android Studio.</p>
+
+<h2>Lessons</h2>
+
+<dl>
+ <dt><a href="{@docRoot}training/wearables/ui/layouts.html">Defining Layouts</a></dt>
+ <dd>Learn how to create layouts that look good on round and square Android Wear devices.</dd>
+ <dt><a href="{@docRoot}training/wearables/ui/cards.html">Creating Cards</a></dt>
+ <dd>Learn how to create cards with custom layouts.</dd>
+ <dt><a href="{@docRoot}training/wearables/ui/lists.html">Creating Lists</a></dt>
+ <dd>Learn how to create lists that are optimized for wearable devices.</dd>
+ <dt><a href="{@docRoot}training/wearables/ui/2d-picker.html">Creating a 2D Picker</a></dt>
+ <dd>Learn how to implement the 2D Picker UI pattern to navigate through pages of data.</dd>
+ <dt><a href="{@docRoot}training/wearables/ui/confirm.html">Showing Confirmations</a></dt>
+ <dd>Learn how to display confirmation animations when users complete actions.</dd>
+ <dt><a href="{@docRoot}training/wearables/ui/exit.html">Exiting Full-Screen Activities</a></dt>
+ <dd>Learn how to implement the long-press-to-dismiss UI pattern to exit full-screen activities.</dd>
+</dl>
\ No newline at end of file
diff --git a/docs/html/training/wearables/ui/layouts.jd b/docs/html/training/wearables/ui/layouts.jd
new file mode 100644
index 0000000..14b9403
--- /dev/null
+++ b/docs/html/training/wearables/ui/layouts.jd
@@ -0,0 +1,261 @@
+page.title=Defining Layouts
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#add-library">Add the Wearable UI Library</a></li>
+ <li><a href="#different-layouts">Specify Different Layouts for Square and Round Screens</a></li>
+ <li><a href="#same-layout">Use a Shape-Aware Layout</a></li>
+</ol>
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+</ul>
+<h2>Video</h2>
+<ul>
+ <li><a href="https://www.youtube.com/watch?v=naf_WbtFAlY">Full Screen Apps for Android Wear</a></li>
+</ul>
+</div>
+</div>
+
+<p>Wearables use the same layout techniques as handheld Android devices, but need to be designed
+with specific constraints. Do not port functionality and the UI from a handheld app and expect a
+good experience. For more information on how to design great wearable apps, read the
+<a href="{@docRoot}design/wear/index.html">Android Wear Design Guidelines</a>.</p>
+
+<p>When you create layouts for Android Wear apps, you need to account for devices with square
+and round screens. Any content placed near the corners of the screen may be cropped on round
+Android Wear devices, so layouts designed for square screens do not work well on round devices.
+For a demonstration of this type of problem, see the video
+<a href="https://www.youtube.com/watch?v=naf_WbtFAlY">Full Screen Apps for Android Wear</a>.</p>
+
+<p>For example, figure 1 shows how the following layout looks on square and round screens:</p>
+
+<img src="{@docRoot}wear/images/01_uilib.png" alt="" width="500" height="261"/>
+<p class="img-caption"><strong>Figure 1.</strong> Demonstration of how a layout designed for
+square screens does not work well on round screens.</p>
+
+<pre>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/hello_square" />
+</LinearLayout>
+</pre>
+
+<p>The text does not display correctly on devices with round screens.</p>
+
+<p>The Wearable UI Library provides two different approaches to solve this problem:</p>
+
+<ul>
+<li>Define different layouts for square and round devices. Your app detects the shape
+ of the device screen and inflates the correct layout at runtime.</li>
+<li>Use a special layout included in the library for both square and round devices. This layout
+ applies different window insets depending on the shape of the device screen.</li>
+</ul>
+
+<p>You typically use the first approach when you want your app to look different depending on
+the shape of the device screen. You use the second approach when you want to use a similar layout
+on both screen shapes without having views cropped near the edges of round screens.</p>
+
+
+<h2 id="add-library">Add the Wearable UI Library</h2>
+
+<p>Android Studio includes the Wearable UI Library on your <code>wear</code> module by default
+when you use the Project Wizard. To compile your project with this library, ensure that the
+<em>Extras</em> > <em>Google Repository</em> package is installed in
+the Android SDK manager and that the following dependency is included in the
+<code>build.gradle</code> file of your <code>wear</code> module:</p>
+
+<pre>
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ <strong>compile 'com.google.android.support:wearable:+'</strong>
+ compile 'com.google.android.gms:play-services-wearable:+'
+}
+</pre>
+
+<p>The <code>'com.google.android.support:wearable'</code> dependency is required to implement
+the layout techniques shown in the following sections.</p>
+
+<p><a href="/shareables/training/wearable-support-docs.zip">Download the full API
+reference documentation</a> for the Wearable UI Library classes.</p>
+
+
+<h2 id="different-layouts">Specify Different Layouts for Square and Round Screens</h2>
+
+<p>The <code>WatchViewStub</code> class included in the Wearable UI Library lets you specify
+different layout definitions for square and round screens. This class detects the screen shape
+at runtime and inflates the corresponding layout.</p>
+
+<p>To use this class for handling different screen shapes in your app:</p>
+
+<ol>
+<li>Add <code>WatchViewStub</code> as the main element of your activity's layout.</li>
+<li>Specify a layout definition file for square screens with the <code>rectLayout</code>
+ attribute.</li>
+<li>Specify a layout definition file for round screens with the <code>roundLayout</code>
+ attribute.</li>
+</ol>
+
+<p>Define your activity's layout as follows:</p>
+
+<pre>
+<android.support.wearable.view.WatchViewStub
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/watch_view_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ <strong>app:rectLayout="@layout/rect_activity_wear"</strong>
+ <strong>app:roundLayout="@layout/round_activity_wear"</strong>>
+</android.support.wearable.view.WatchViewStub>
+</pre>
+
+<p>Inflate this layout in your activity:</p>
+
+<pre>
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_wear);
+}
+</pre>
+
+<p>Then create different layout definition files for square and round screens. In this example,
+you need to create the files <code>res/layout/rect_activity_wear.xml</code> and
+<code>res/layout/round_activity_wear.xml</code>. You define these layouts in the same way that
+you create layouts for handheld apps, but taking into account the constraints of wearable devices.
+The system inflates the correct layout at runtime depending on the screen shape.</p>
+
+<h3>Accessing layout views</h3>
+
+<p>The layouts that you specify for square or round screens are not inflated until
+<code>WatchViewStub</code> detects the shape of the screen, so your app cannot access their views
+immediately. To access these views, set a listener in your activity to be notified when
+the shape-specific layout has been inflated:</p>
+
+<pre>
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_wear);
+
+ WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
+ stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
+ @Override public void onLayoutInflated(WatchViewStub stub) {
+ // Now you can access your views
+ TextView tv = (TextView) stub.findViewById(R.id.text);
+ ...
+ }
+ });
+}
+</pre>
+
+
+<h2 id="same-layout">Use a Shape-Aware Layout</h2>
+
+<div style="float:right;margin-left:25px;width:250px">
+<img src="{@docRoot}wear/images/02_uilib.png" width="250" height="250" alt=""/>
+<p class="img-caption"><strong>Figure 2.</strong> Window insets on a round screen.</p>
+</div>
+
+<p>The <code>BoxInsetLayout</code> class included in the Wearable UI Library extends
+{@link android.widget.FrameLayout} and lets you define a single layout that works for both square
+and round screens. This class applies the required window insets depending on the screen shape
+and lets you easily align views on the center or near the edges of the screen.</p>
+
+<p>The gray square in figure 2 shows the area where <code>BoxInsetLayout</code> can automatically
+place its child views on round screens after applying the required window insets. To be displayed
+inside this area, children views specify the <code>layout_box</code> atribute with these values:
+</p>
+
+<ul>
+<li>A combination of <code>top</code>, <code>bottom</code>, <code>left</code>, and
+ <code>right</code>. For example, <code>"left|top"</code> positions the child's left and top
+ edges inside the gray square in figure 2.</li>
+<li>The <code>all</code> value positions all the child's content inside the gray square in
+ figure 2.</li>
+</ul>
+
+<p>On square screens, the window insets are zero and the <code>layout_box</code> attribute is
+ignored.</p>
+
+<img src="{@docRoot}wear/images/03_uilib.png" width="500" height="253" alt=""/>
+<p class="img-caption"><strong>Figure 3.</strong> A layout definition that works on both
+square and round screens.</p>
+
+<p>The layout shown in figure 3 uses <code>BoxInsetLayout</code> and works on square and
+round screens:</p>
+
+<pre>
+<<strong>android.support.wearable.view.BoxInsetLayout</strong>
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ <strong>android:background="@drawable/robot_background"</strong>
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ <strong>android:padding="15dp"</strong>>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ <strong>android:padding="5dp"</strong>
+ <strong>app:layout_box="all"</strong>>
+
+ <TextView
+ android:gravity="center"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@string/sometext"
+ android:textColor="@color/black" />
+
+ <ImageButton
+ android:background="@null"
+ android:layout_gravity="bottom|left"
+ android:layout_height="50dp"
+ android:layout_width="50dp"
+ android:src="@drawable/ok" />
+
+ <ImageButton
+ android:background="@null"
+ android:layout_gravity="bottom|right"
+ android:layout_height="50dp"
+ android:layout_width="50dp"
+ android:src="@drawable/cancel" />
+ </FrameLayout>
+</android.support.wearable.view.BoxInsetLayout>
+</pre>
+
+<p>Notice the parts of the layout marked in bold:</p>
+
+<ul>
+<li>
+ <p><code>android:padding="15dp"</code></p>
+ <p>This line assigns padding to the <code>BoxInsetLayout</code> element. Because the window
+ insets on round devices are larger than 15dp, this padding only applies to square screens.</p>
+</li>
+<li>
+ <p><code>android:padding="5dp"</code></p>
+ <p>This line assigns padding to the inner <code>FrameLayout</code> element. This padding applies
+ to both square and round screens. The total padding between the buttons and the window insets
+ is 20 dp on square screens (15+5) and 5 dp on round screens.</p>
+</li>
+<li>
+ <p><code>app:layout_box="all"</code></p>
+ <p>This line ensures that the <code>FrameLayout</code> element and its children are boxed inside
+ the area defined by the window insets on round screens. This line has no effect on square
+ screens.</p>
+</li>
+</ul>
\ No newline at end of file
diff --git a/docs/html/training/wearables/ui/lists.jd b/docs/html/training/wearables/ui/lists.jd
new file mode 100644
index 0000000..5458541
--- /dev/null
+++ b/docs/html/training/wearables/ui/lists.jd
@@ -0,0 +1,304 @@
+page.title=Creating Lists
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#add-list">Add a List View</a></li>
+ <li><a href="#layout-impl">Create a Layout Implementation for List Items</a></li>
+ <li><a href="#layout-def">Create a Layout Definition for Items</a></li>
+ <li><a href="#adapter">Create an Adapter to Populate the List</a></li>
+ <li><a href="#adapter-listener">Associate the Adapter and Set a Click Listener</a></li>
+</ol>
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+</ul>
+</div>
+</div>
+
+
+<p>Lists let users select an item from a set of choices easily on wearable devices. This lesson
+shows you how to create lists in your Android Wear apps.</p>
+
+<p>The Wearable UI Library includes the <code>WearableListView</code> class, which is a list
+implementation optimized for wearable devices..</p>
+
+<p class="note"><strong>Note:</strong> The <em>Notifications</em> sample in the Android SDK
+demonstrates how to use <code>WearableListView</code> in your apps. This sample is located in
+the <code>android-sdk/samples/android-20/wearable/Notifications</code> directory.</p>
+
+<p>To create a list in your Android Wear apps:</p>
+
+<ol>
+<li>Add a <code>WearableListView</code> element to your activity's layout definition.</li>
+<li>Create a custom layout implementation for your list items.</li>
+<li>Use this implementation to create a layout definition file for your list items.</li>
+<div style="float:right;margin-left:25px;width:215px;margin-top:-20px">
+<img src="{@docRoot}wear/images/06_uilib.png" width="200" height="200" alt=""/>
+<p class="img-caption" style="text-align:center"><strong>Figure 3:</strong>
+A list view on Android Wear.</p>
+</div>
+<li>Create an adapter to populate the list.</li>
+<li>Assign the adapter to the <code>WearableListView</code> element.</li>
+</ol>
+
+<p>These steps are described in detail in the following sections.</p>
+
+
+<h2 id="add-list">Add a List View</h2>
+
+<p>The following layout adds a list view to an activity using a <code>BoxInsetLayout</code>, so
+the list is displayed properly on both round and square devices:</p>
+
+<pre>
+<android.support.wearable.view.BoxInsetLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:background="@drawable/robot_background"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <FrameLayout
+ android:id="@+id/frame_layout"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ app:layout_box="left|bottom|right">
+
+ <<strong>android.support.wearable.view.WearableListView</strong>
+ android:id="@+id/wearable_list"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+ </android.support.wearable.view.WearableListView>
+ </FrameLayout>
+</android.support.wearable.view.BoxInsetLayout>
+</pre>
+
+
+<h2 id="layout-impl">Create a Layout Implementation for List Items</h2>
+
+<p>In many cases, each list item consists of an icon and a description. The
+<em>Notifications</em> sample from the Android SDK implements a custom layout that extends
+{@link android.widget.LinearLayout} to incorporate these two elements inside each list item.
+This layout also implements the methods in the <code>WearableListView.Item</code> interface
+to animate the item's icon and fade the text in response to events from
+<code>WearableListView</code> as the user scrolls through the list.</p>
+
+<pre>
+public class WearableListItemLayout extends LinearLayout
+ implements WearableListView.Item {
+
+ private final float mFadedTextAlpha;
+ private final int mFadedCircleColor;
+ private final int mChosenCircleColor;
+ private ImageView mCircle;
+ private float mScale;
+ private TextView mName;
+
+ public WearableListItemLayout(Context context) {
+ this(context, null);
+ }
+
+ public WearableListItemLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public WearableListItemLayout(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ mFadedTextAlpha = getResources()
+ .getInteger(R.integer.action_text_faded_alpha) / 100f;
+ mFadedCircleColor = getResources().getColor(R.color.grey);
+ mChosenCircleColor = getResources().getColor(R.color.blue);
+ }
+
+ // Get references to the icon and text in the item layout definition
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ // These are defined in the layout file for list items
+ // (see next section)
+ mCircle = (ImageView) findViewById(R.id.circle);
+ mName = (TextView) findViewById(R.id.name);
+ }
+
+ // Provide scaling values for WearableListView animations
+ @Override
+ public float getProximityMinValue() {
+ return 1f;
+ }
+
+ @Override
+ public float getProximityMaxValue() {
+ return 1.6f;
+ }
+
+ @Override
+ public float getCurrentProximityValue() {
+ return mScale;
+ }
+
+ // Scale the icon for WearableListView animations
+ @Override
+ public void setScalingAnimatorValue(float scale) {
+ mScale = scale;
+ mCircle.setScaleX(scale);
+ mCircle.setScaleY(scale);
+ }
+
+ // Change color of the icon, remove fading from the text
+ @Override
+ public void onScaleUpStart() {
+ mName.setAlpha(1f);
+ ((GradientDrawable) mCircle.getDrawable()).setColor(mChosenCircleColor);
+ }
+
+ // Change the color of the icon, fade the text
+ @Override
+ public void onScaleDownStart() {
+ ((GradientDrawable) mCircle.getDrawable()).setColor(mFadedCircleColor);
+ mName.setAlpha(mFadedTextAlpha);
+ }
+}
+</pre>
+
+
+<h2 id="layout-def">Create a Layout Definition for Items</h2>
+
+<p>After you implement a custom layout for list items, you provide a layout definition file that
+specifies the layout parameters of each of the components inside a list item. The following layout
+definition uses the custom layout implementation from the previous section and defines an icon
+and a text view whose IDs match those in the layout implementation class:</p>
+
+<pre>
+<-- res/layout/list_item.xml -->
+<com.example.android.support.wearable.notifications.WearableListItemLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:gravity="center_vertical"
+ android:layout_width="match_parent"
+ android:layout_height="80dp">
+ <ImageView
+ android:id="@+id/circle"
+ android:layout_height="20dp"
+ android:layout_margin="16dp"
+ android:layout_width="20dp"
+ android:src="@drawable/wl_circle"/>
+ <TextView
+ android:id="@+id/name"
+ android:gravity="center_vertical|left"
+ android:layout_width="wrap_content"
+ android:layout_marginRight="16dp"
+ android:layout_height="match_parent"
+ android:fontFamily="sans-serif-condensed-light"
+ android:lineSpacingExtra="-4sp"
+ android:textColor="@color/text_color"
+ android:textSize="16sp"/>
+</com.example.android.support.wearable.notifications.WearableListItemLayout>
+</pre>
+
+
+<h2 id="adapter">Create an Adapter to Populate the List</h2>
+
+<p>The adapter populates the <code>WearableListView</code> with content. The following simple
+adapter populates the list with elements based on an array of strings:</p>
+
+<pre>
+private static final class Adapter extends WearableListView.Adapter {
+ private String[] mDataset;
+ private final Context mContext;
+ private final LayoutInflater mInflater;
+
+ // Provide a suitable constructor (depends on the kind of dataset)
+ public Adapter(Context context, String[] dataset) {
+ mContext = context;
+ mInflater = LayoutInflater.from(context);
+ mDataset = dataset;
+ }
+
+ // Provide a reference to the type of views you're using
+ public static class ItemViewHolder extends WearableListView.ViewHolder {
+ private TextView textView;
+ public ItemViewHolder(View itemView) {
+ super(itemView);
+ // find the text view within the custom item's layout
+ textView = (TextView) itemView.findViewById(R.id.name);
+ }
+ }
+
+ // Create new views for list items
+ // (invoked by the WearableListView's layout manager)
+ @Override
+ public WearableListView.ViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ // Inflate our custom layout for list items
+ return new ItemViewHolder(mInflater.inflate(R.layout.list_item, null));
+ }
+
+ // Replace the contents of a list item
+ // Instead of creating new views, the list tries to recycle existing ones
+ // (invoked by the WearableListView's layout manager)
+ @Override
+ public void onBindViewHolder(WearableListView.ViewHolder holder,
+ int position) {
+ // retrieve the text view
+ ItemViewHolder itemHolder = (ItemViewHolder) holder;
+ TextView view = itemHolder.textView;
+ // replace text contents
+ view.setText(mDataset[position]);
+ // replace list item's metadata
+ holder.itemView.setTag(position);
+ }
+
+ // Return the size of your dataset
+ // (invoked by the WearableListView's layout manager)
+ @Override
+ public int getItemCount() {
+ return mDataset.length;
+ }
+}
+</pre>
+
+
+<h2 id="adapter-listener">Associate the Adapter and Set a Click Listener</h2>
+
+<p>In your activity, obtain a reference to the <code>WearableListView</code> element from
+your layout, assign an instance of the adapter to populate the list, and set a click listener
+to complete an action when the user selects a particular list item.</p>
+
+<pre>
+public class WearActivity extends Activity
+ implements WearableListView.ClickListener {
+
+ // Sample dataset for the list
+ String[] elements = { "List Item 1", "List Item 2", ... };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.my_list_activity);
+
+ // Get the list component from the layout of the activity
+ WearableListView listView =
+ (WearableListView) findViewById(R.id.wearable_list);
+
+ // Assign an adapter to the list
+ listView.setAdapter(new Adapter(this, elements));
+
+ // Set a click listener
+ listView.setClickListener(this);
+ }
+
+ // WearableListView click listener
+ @Override
+ public void onClick(WearableListView.ViewHolder v) {
+ Integer tag = (Integer) v.itemView.getTag();
+ // use this data to complete some action ...
+ }
+
+ @Override
+ public void onTopEmptyRegionClick() {
+ }
+}
+</pre>
diff --git a/docs/html/wear/images/01_uilib.png b/docs/html/wear/images/01_uilib.png
new file mode 100644
index 0000000..14484d6
--- /dev/null
+++ b/docs/html/wear/images/01_uilib.png
Binary files differ
diff --git a/docs/html/wear/images/02_uilib.png b/docs/html/wear/images/02_uilib.png
new file mode 100644
index 0000000..39896a1
--- /dev/null
+++ b/docs/html/wear/images/02_uilib.png
Binary files differ
diff --git a/docs/html/wear/images/03_uilib.png b/docs/html/wear/images/03_uilib.png
new file mode 100644
index 0000000..ebf635d
--- /dev/null
+++ b/docs/html/wear/images/03_uilib.png
Binary files differ
diff --git a/docs/html/wear/images/04_uilib.png b/docs/html/wear/images/04_uilib.png
new file mode 100644
index 0000000..e5a29a0
--- /dev/null
+++ b/docs/html/wear/images/04_uilib.png
Binary files differ
diff --git a/docs/html/wear/images/05_uilib.png b/docs/html/wear/images/05_uilib.png
new file mode 100644
index 0000000..25d2e34
--- /dev/null
+++ b/docs/html/wear/images/05_uilib.png
Binary files differ
diff --git a/docs/html/wear/images/06_uilib.png b/docs/html/wear/images/06_uilib.png
new file mode 100644
index 0000000..257bb82
--- /dev/null
+++ b/docs/html/wear/images/06_uilib.png
Binary files differ
diff --git a/docs/html/wear/images/07_uilib.png b/docs/html/wear/images/07_uilib.png
new file mode 100644
index 0000000..ce1a80c
--- /dev/null
+++ b/docs/html/wear/images/07_uilib.png
Binary files differ
diff --git a/docs/html/wear/images/08_uilib.png b/docs/html/wear/images/08_uilib.png
new file mode 100644
index 0000000..b44522b
--- /dev/null
+++ b/docs/html/wear/images/08_uilib.png
Binary files differ
diff --git a/docs/html/wear/images/09_uilib.png b/docs/html/wear/images/09_uilib.png
new file mode 100644
index 0000000..f8234e1
--- /dev/null
+++ b/docs/html/wear/images/09_uilib.png
Binary files differ
diff --git a/location/java/android/location/package.html b/location/java/android/location/package.html
index 81fcea4..2355e72 100644
--- a/location/java/android/location/package.html
+++ b/location/java/android/location/package.html
@@ -1,22 +1,20 @@
<html>
<body>
-<p>Contains the framework API classes that define Android location-based and related services.</p>
-<p class="note">
- <strong>Note:</strong> The Google Location Services API, part of Google Play
- Services, provides a more powerful, high-level framework that automates tasks such as
- location provider choice and power management. Location Services also provides new
- features such as activity detection that aren't available in the framework API. Developers who
- are using the framework API, as well as developers who are just now adding location-awareness
- to their apps, should strongly consider using the Location Services API.
-<br/>
- To learn more about the Location Services API, see
- <a href="{@docRoot}google/play-services/location.html">Location APIs</a>.
+<p>Contains the framework API classes that define Android location-based and
+ related services.</p>
+<p class="warning">
+<strong>This API is not the recommended method for accessing Android location.</strong><br>
+The
+<a href="{@docRoot}reference/com/google/android/gms/location/package-summary.html">Google Location Services API</a>,
+part of Google Play services, is the preferred way to add location-awareness to
+your app. It offers a simpler API, higher accuracy, low-power geofencing, and
+more. If you are currently using the android.location API, you are strongly
+encouraged to switch to the Google Location Services API as soon as
+possible.
+<br><br>
+To learn more about the Google Location Services API, see the
+<a href="{@docRoot}google/play-services/location.html">Location API overview</a>.
</p>
-
-<p>For more information about the framework API, see the
-<a href="{@docRoot}guide/topics/location/index.html">Location and Maps</a> guide.</p>
-{@more}
-
</body>
</html>
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index ef1c0b0..ce78bb6 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -18,10 +18,15 @@
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
+import java.util.Iterator;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
/**
@@ -99,6 +104,8 @@
private final static String TAG = "android.media.AudioRecord";
+ /** @hide */
+ public final static String SUBMIX_FIXED_VOLUME = "fixedVolume";
//---------------------------------------------------------
// Used exclusively by native code
@@ -184,6 +191,7 @@
* AudioAttributes
*/
private AudioAttributes mAudioAttributes;
+ private boolean mIsSubmixFullVolume = false;
//---------------------------------------------------------
// Constructor, Finalize
@@ -267,6 +275,18 @@
mAudioAttributes = attributes;
+ // is this AudioRecord using REMOTE_SUBMIX at full volume?
+ if (mAudioAttributes.getCapturePreset() == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
+ final Iterator<String> tagsIter = mAudioAttributes.getTags().iterator();
+ while (tagsIter.hasNext()) {
+ if (tagsIter.next().equalsIgnoreCase(SUBMIX_FIXED_VOLUME)) {
+ mIsSubmixFullVolume = true;
+ Log.v(TAG, "Will record from REMOTE_SUBMIX at full fixed volume");
+ break;
+ }
+ }
+ }
+
int rate = 0;
if ((format.getPropertySetMask()
& AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0)
@@ -420,7 +440,8 @@
@Override
protected void finalize() {
- native_finalize();
+ // will cause stop() to be called, and if appropriate, will handle fixed volume recording
+ release();
}
@@ -587,6 +608,7 @@
// start recording
synchronized(mRecordingStateLock) {
if (native_start(MediaSyncEvent.SYNC_EVENT_NONE, 0) == SUCCESS) {
+ handleFullVolumeRec(true);
mRecordingState = RECORDSTATE_RECORDING;
}
}
@@ -609,6 +631,7 @@
// start recording
synchronized(mRecordingStateLock) {
if (native_start(syncEvent.getType(), syncEvent.getAudioSessionId()) == SUCCESS) {
+ handleFullVolumeRec(true);
mRecordingState = RECORDSTATE_RECORDING;
}
}
@@ -626,11 +649,25 @@
// stop recording
synchronized(mRecordingStateLock) {
+ handleFullVolumeRec(false);
native_stop();
mRecordingState = RECORDSTATE_STOPPED;
}
}
+ private final IBinder mICallBack = new Binder();
+ private void handleFullVolumeRec(boolean starting) {
+ if (!mIsSubmixFullVolume) {
+ return;
+ }
+ final IBinder b = ServiceManager.getService(android.content.Context.AUDIO_SERVICE);
+ final IAudioService ias = IAudioService.Stub.asInterface(b);
+ try {
+ ias.forceRemoteSubmixFullVolume(starting, mICallBack);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error talking to AudioService when handling full submix volume", e);
+ }
+ }
//---------------------------------------------------------
// Audio data supply
@@ -881,6 +918,7 @@
int sampleRate, int channelMask, int audioFormat,
int buffSizeInBytes, int[] sessionId);
+ // TODO remove: implementation calls directly into implementation of native_release()
private native final void native_finalize();
private native final void native_release();
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 5c2abc53..96ce2c1 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -485,6 +485,7 @@
AudioSystem.DEVICE_OUT_HDMI_ARC |
AudioSystem.DEVICE_OUT_SPDIF |
AudioSystem.DEVICE_OUT_AUX_LINE;
+ int mFullVolumeDevices = 0;
// TODO merge orientation and rotation
private final boolean mMonitorOrientation;
@@ -727,6 +728,10 @@
}
}
+ private void checkAllFixedVolumeDevices(int streamType) {
+ mStreamStates[streamType].checkFixedVolumeDevices();
+ }
+
private void createStreamStates() {
int numStreamTypes = AudioSystem.getNumStreamTypes();
VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
@@ -1466,6 +1471,106 @@
return mStreamStates[streamType].isMuted();
}
+ private class RmtSbmxFullVolDeathHandler implements IBinder.DeathRecipient {
+ private IBinder mICallback; // To be notified of client's death
+
+ RmtSbmxFullVolDeathHandler(IBinder cb) {
+ mICallback = cb;
+ try {
+ cb.linkToDeath(this, 0/*flags*/);
+ } catch (RemoteException e) {
+ Log.e(TAG, "can't link to death", e);
+ }
+ }
+
+ boolean isHandlerFor(IBinder cb) {
+ return mICallback.equals(cb);
+ }
+
+ void forget() {
+ try {
+ mICallback.unlinkToDeath(this, 0/*flags*/);
+ } catch (NoSuchElementException e) {
+ Log.e(TAG, "error unlinking to death", e);
+ }
+ }
+
+ public void binderDied() {
+ Log.w(TAG, "Recorder with remote submix at full volume died " + mICallback);
+ forceRemoteSubmixFullVolume(false, mICallback);
+ }
+ }
+
+ /**
+ * call must be synchronized on mRmtSbmxFullVolDeathHandlers
+ * @return true if there is a registered death handler, false otherwise */
+ private boolean discardRmtSbmxFullVolDeathHandlerFor(IBinder cb) {
+ Iterator<RmtSbmxFullVolDeathHandler> it = mRmtSbmxFullVolDeathHandlers.iterator();
+ while (it.hasNext()) {
+ final RmtSbmxFullVolDeathHandler handler = it.next();
+ if (handler.isHandlerFor(cb)) {
+ handler.forget();
+ mRmtSbmxFullVolDeathHandlers.remove(handler);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** call synchronized on mRmtSbmxFullVolDeathHandlers */
+ private boolean hasRmtSbmxFullVolDeathHandlerFor(IBinder cb) {
+ Iterator<RmtSbmxFullVolDeathHandler> it = mRmtSbmxFullVolDeathHandlers.iterator();
+ while (it.hasNext()) {
+ if (it.next().isHandlerFor(cb)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int mRmtSbmxFullVolRefCount = 0;
+ private ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers =
+ new ArrayList<RmtSbmxFullVolDeathHandler>();
+
+ public void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb) {
+ if (cb == null) {
+ return;
+ }
+ if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.CAPTURE_AUDIO_OUTPUT))) {
+ Log.w(TAG, "Trying to call forceRemoteSubmixFullVolume() without CAPTURE_AUDIO_OUTPUT");
+ return;
+ }
+ synchronized(mRmtSbmxFullVolDeathHandlers) {
+ boolean applyRequired = false;
+ if (startForcing) {
+ if (!hasRmtSbmxFullVolDeathHandlerFor(cb)) {
+ mRmtSbmxFullVolDeathHandlers.add(new RmtSbmxFullVolDeathHandler(cb));
+ if (mRmtSbmxFullVolRefCount == 0) {
+ mFullVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+ mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+ applyRequired = true;
+ }
+ mRmtSbmxFullVolRefCount++;
+ }
+ } else {
+ if (discardRmtSbmxFullVolDeathHandlerFor(cb) && (mRmtSbmxFullVolRefCount > 0)) {
+ mRmtSbmxFullVolRefCount--;
+ if (mRmtSbmxFullVolRefCount == 0) {
+ mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+ mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+ applyRequired = true;
+ }
+ }
+ }
+ if (applyRequired) {
+ // Assumes only STREAM_MUSIC going through DEVICE_OUT_REMOTE_SUBMIX
+ checkAllFixedVolumeDevices(AudioSystem.STREAM_MUSIC);
+ mStreamStates[AudioSystem.STREAM_MUSIC].applyAllVolumes();
+ }
+ }
+ }
+
/** @see AudioManager#setMasterMute(boolean, int) */
public void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb) {
if (mUseFixedVolume) {
@@ -3243,8 +3348,8 @@
int index;
if (isMuted()) {
index = 0;
- } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
- mAvrcpAbsVolSupported) {
+ } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported)
+ || ((device & mFullVolumeDevices) != 0)) {
index = (mIndexMax + 5)/10;
} else {
index = (getIndex(device) + 5)/10;
@@ -3272,8 +3377,10 @@
if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
if (isMuted()) {
index = 0;
- } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
- mAvrcpAbsVolSupported) {
+ } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
+ mAvrcpAbsVolSupported)
+ || ((device & mFullVolumeDevices) != 0))
+ {
index = (mIndexMax + 5)/10;
} else {
index = ((Integer)entry.getValue() + 5)/10;
@@ -3403,7 +3510,8 @@
Map.Entry entry = (Map.Entry)i.next();
int device = ((Integer)entry.getKey()).intValue();
int index = ((Integer)entry.getValue()).intValue();
- if (((device & mFixedVolumeDevices) != 0) && index != 0) {
+ if (((device & mFullVolumeDevices) != 0)
+ || (((device & mFixedVolumeDevices) != 0) && index != 0)) {
entry.setValue(mIndexMax);
}
applyDeviceVolume(device);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 75fc03c..1c41432 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -56,6 +56,8 @@
boolean isStreamMute(int streamType);
+ void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb);
+
void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb);
boolean isMasterMute();
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index b3a6e88..7a262de 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -17,16 +17,24 @@
package com.android.captiveportallogin;
import android.app.Activity;
+import android.app.LoadedApk;
+import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.Proxy;
+import android.net.ProxyInfo;
+import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.util.ArrayMap;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -42,8 +50,11 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.lang.InterruptedException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
public class CaptivePortalLoginActivity extends Activity {
+ private static final String TAG = "CaptivePortalLogin";
private static final String DEFAULT_SERVER = "clients3.google.com";
private static final int SOCKET_TIMEOUT_MS = 10000;
@@ -72,14 +83,34 @@
done(true);
}
- setContentView(R.layout.activity_captive_portal_login);
-
- getActionBar().setDisplayShowHomeEnabled(false);
-
mNetId = Integer.parseInt(getIntent().getStringExtra(Intent.EXTRA_TEXT));
final Network network = new Network(mNetId);
ConnectivityManager.setProcessDefaultNetwork(network);
+ // Set HTTP proxy system properties to those of the selected Network.
+ final LinkProperties lp = ConnectivityManager.from(this).getLinkProperties(network);
+ if (lp != null) {
+ final ProxyInfo proxyInfo = lp.getHttpProxy();
+ String host = "";
+ String port = "";
+ String exclList = "";
+ Uri pacFileUrl = Uri.EMPTY;
+ if (proxyInfo != null) {
+ host = proxyInfo.getHost();
+ port = Integer.toString(proxyInfo.getPort());
+ exclList = proxyInfo.getExclusionListAsString();
+ pacFileUrl = proxyInfo.getPacFileUrl();
+ }
+ Proxy.setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
+ Log.v(TAG, "Set proxy system properties to " + proxyInfo);
+ }
+
+ // Proxy system properties must be initialized before setContentView is called because
+ // setContentView initializes the WebView logic which in turn reads the system properties.
+ setContentView(R.layout.activity_captive_portal_login);
+
+ getActionBar().setDisplayShowHomeEnabled(false);
+
// Exit app if Network disappears.
final NetworkCapabilities networkCapabilities =
ConnectivityManager.from(this).getNetworkCapabilities(network);
@@ -99,12 +130,39 @@
}
ConnectivityManager.from(this).registerNetworkCallback(builder.build(), mNetworkCallback);
- WebView myWebView = (WebView) findViewById(R.id.webview);
+ final WebView myWebView = (WebView) findViewById(R.id.webview);
+ myWebView.clearCache(true);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
myWebView.setWebViewClient(new MyWebViewClient());
myWebView.setWebChromeClient(new MyWebChromeClient());
- myWebView.loadUrl(mURL.toString());
+ // Start initial page load so WebView finishes loading proxy settings.
+ // Actual load of mUrl is initiated by MyWebViewClient.
+ myWebView.loadData("", "text/html", null);
+ }
+
+ // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties.
+ private void setWebViewProxy() {
+ LoadedApk loadedApk = getApplication().mLoadedApk;
+ try {
+ Field receiversField = LoadedApk.class.getDeclaredField("mReceivers");
+ receiversField.setAccessible(true);
+ ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
+ for (Object receiverMap : receivers.values()) {
+ for (Object rec : ((ArrayMap) receiverMap).keySet()) {
+ Class clazz = rec.getClass();
+ if (clazz.getName().contains("ProxyChangeListener")) {
+ Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class,
+ Intent.class);
+ Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
+ onReceiveMethod.invoke(rec, getApplicationContext(), intent);
+ Log.v(TAG, "Prompting WebView proxy reload.");
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while setting WebView proxy: " + e);
+ }
}
private void done(boolean use_network) {
@@ -176,13 +234,25 @@
}
private class MyWebViewClient extends WebViewClient {
+ private boolean firstPageLoad = true;
+
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ if (firstPageLoad) return;
testForCaptivePortal();
}
@Override
public void onPageFinished(WebView view, String url) {
+ if (firstPageLoad) {
+ firstPageLoad = false;
+ // Now that WebView has loaded at least one page we know it has read in the proxy
+ // settings. Now prompt the WebView read the Network-specific proxy settings.
+ setWebViewProxy();
+ // Load the real page.
+ view.loadUrl(mURL.toString());
+ return;
+ }
testForCaptivePortal();
}
}
diff --git a/packages/SystemUI/res/values-km-rKH/strings.xml b/packages/SystemUI/res/values-km-rKH/strings.xml
index 31fb4637..100ef5e 100644
--- a/packages/SystemUI/res/values-km-rKH/strings.xml
+++ b/packages/SystemUI/res/values-km-rKH/strings.xml
@@ -69,7 +69,7 @@
<string name="screenshot_saving_ticker" msgid="7403652894056693515">"កំពុងរក្សាទុករូបថតអេក្រង់…"</string>
<string name="screenshot_saving_title" msgid="8242282144535555697">"កំពុងរក្សាទុករូបថតអេក្រង់..."</string>
<string name="screenshot_saving_text" msgid="2419718443411738818">"រូបថតអេក្រង់កំពុងត្រូវបានរក្សាទុក។"</string>
- <string name="screenshot_saved_title" msgid="6461865960961414961">"បានចាប់យករូបថតអេក្រង់។"</string>
+ <string name="screenshot_saved_title" msgid="6461865960961414961">"បានចាប់យករូបថតអេក្រង់។"</string>
<string name="screenshot_saved_text" msgid="1152839647677558815">"ប៉ះ ដើម្បីមើលរូបថតអេក្រង់របស់អ្នក។"</string>
<string name="screenshot_failed_title" msgid="705781116746922771">"មិនអាចចាប់យករូបថតអេក្រង់។"</string>
<string name="screenshot_failed_text" msgid="1260203058661337274">"មិនអាចថតអេក្រង់ដោយសារតែទំហំផ្ទុកមានដែនកំណត់ ឬវាមិនត្រូវបានអនុញ្ញាតដោយកម្មវិធី ឬស្ថាប័នរបស់អ្នក។"</string>
@@ -152,7 +152,7 @@
<string name="accessibility_remove_notification" msgid="3603099514902182350">"សម្អាតការជូនដំណឹង។"</string>
<string name="accessibility_gps_enabled" msgid="3511469499240123019">"បានបើក GPS ។"</string>
<string name="accessibility_gps_acquiring" msgid="8959333351058967158">"ទទួល GPS ។"</string>
- <string name="accessibility_tty_enabled" msgid="4613200365379426561">"បានបើកម៉ាស៊ីនអង្គុលីលេខ"</string>
+ <string name="accessibility_tty_enabled" msgid="4613200365379426561">"បានបើកម៉ាស៊ីនអង្គុលីលេខ"</string>
<string name="accessibility_ringer_vibrate" msgid="666585363364155055">"កម្មវិធីរោទ៍ញ័រ។"</string>
<string name="accessibility_ringer_silent" msgid="9061243307939135383">"កម្មវិធីរោទ៍ស្ងាត់។"</string>
<!-- no translation found for accessibility_casting (6887382141726543668) -->
@@ -236,7 +236,7 @@
<string name="quick_settings_rotation_locked_portrait_label" msgid="5102691921442135053">"បញ្ឈរ"</string>
<string name="quick_settings_rotation_locked_landscape_label" msgid="8553157770061178719">"ទេសភាព"</string>
<string name="quick_settings_ime_label" msgid="7073463064369468429">"វិធីសាស្ត្របញ្ចូល"</string>
- <string name="quick_settings_location_label" msgid="5011327048748762257">"ទីតាំង"</string>
+ <string name="quick_settings_location_label" msgid="5011327048748762257">"ទីតាំង"</string>
<string name="quick_settings_location_off_label" msgid="7464544086507331459">"ទីតាំងបានបិទ"</string>
<string name="quick_settings_media_device_label" msgid="1302906836372603762">"ឧបករណ៍មេឌៀ"</string>
<string name="quick_settings_rssi_label" msgid="7725671335550695589">"RSSI"</string>
@@ -280,11 +280,11 @@
<string name="recents_lock_to_app_button_label" msgid="4793991421811647489">"ចាក់សោទៅកម្មវិធី"</string>
<string name="recents_search_bar_label" msgid="8074997400187836677">"ស្វែងរក"</string>
<string name="recents_launch_error_message" msgid="2969287838120550506">"មិនអាចចាប់ផ្ដើម <xliff:g id="APP">%s</xliff:g> ទេ។"</string>
- <string name="expanded_header_battery_charged" msgid="5945855970267657951">"បានបញ្ចូលថ្ម"</string>
+ <string name="expanded_header_battery_charged" msgid="5945855970267657951">"បានបញ្ចូលថ្ម"</string>
<string name="expanded_header_battery_charging" msgid="205623198487189724">"កំពុងបញ្ចូលថ្ម"</string>
<string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> រហូតដល់ពេញ"</string>
<string name="expanded_header_battery_not_charging" msgid="4798147152367049732">"មិនកំពុងបញ្ចូលថ្ម"</string>
- <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"បណ្ដាញអាច\nត្រូវបានត្រួតពិនិត្យ"</string>
+ <string name="ssl_ca_cert_warning" msgid="9005954106902053641">"បណ្ដាញអាច\nត្រូវបានត្រួតពិនិត្យ"</string>
<string name="description_target_search" msgid="3091587249776033139">"ស្វែងរក"</string>
<string name="description_direction_up" msgid="7169032478259485180">"រុញឡើងលើដើម្បី <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ។"</string>
<string name="description_direction_left" msgid="7207478719805562165">"រុញទៅឆ្វេងដើម្បី <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g> ។"</string>
diff --git a/packages/SystemUI/res/values-lo-rLA/strings.xml b/packages/SystemUI/res/values-lo-rLA/strings.xml
index 502ec44..746dd71 100644
--- a/packages/SystemUI/res/values-lo-rLA/strings.xml
+++ b/packages/SystemUI/res/values-lo-rLA/strings.xml
@@ -314,7 +314,7 @@
<string name="guest_exit_guest" msgid="7187359342030096885">"ລຶບແຂກ"</string>
<string name="guest_exit_guest_dialog_title" msgid="8480693520521766688">"ລຶບແຂກບໍ?"</string>
<string name="guest_exit_guest_dialog_message" msgid="4155503224769676625">"ແອັບຯແລະຂໍ້ມູນທັງໝົດໃນເຊດຊັນນີ້ຈະຖືກລຶບອອກ."</string>
- <string name="guest_exit_guest_dialog_remove" msgid="7402231963862520531">"ລຶບ"</string>
+ <string name="guest_exit_guest_dialog_remove" msgid="7402231963862520531">"ລຶບ"</string>
<string name="guest_wipe_session_title" msgid="6419439912885956132">"ຍິນດີຕ້ອນຮັບກັບມາ, ຜູ່ຢ້ຽມຢາມ!"</string>
<string name="guest_wipe_session_message" msgid="8476238178270112811">"ທ່ານຕ້ອງການສືບຕໍ່ເຊດຊັນຂອງທ່ານບໍ່?"</string>
<string name="guest_wipe_session_wipe" msgid="5065558566939858884">"ເລີ່ມຕົ້ນໃຫມ່"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 91dcaab..e66522f 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -265,7 +265,7 @@
<string name="quick_settings_tethering_label" msgid="7153452060448575549">"Kusambaza mtandao"</string>
<string name="quick_settings_hotspot_label" msgid="6046917934974004879">"Mtandao-hewa"</string>
<string name="quick_settings_notifications_label" msgid="4818156442169154523">"Arifa"</string>
- <string name="quick_settings_flashlight_label" msgid="2133093497691661546">"Kurunzi"</string>
+ <string name="quick_settings_flashlight_label" msgid="2133093497691661546">"Tochi"</string>
<string name="quick_settings_cellular_detail_title" msgid="8575062783675171695">"Data ya simu ya mkononi"</string>
<string name="quick_settings_cellular_detail_data_usage" msgid="1964260360259312002">"Matumizi ya data"</string>
<string name="quick_settings_cellular_detail_remaining_data" msgid="722715415543541249">"Data iliyosalia"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 954046c..fcdbfc1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -48,6 +48,8 @@
private static SummaryStats sScreenOnPulsingStats;
private static SummaryStats sScreenOnNotPulsingStats;
private static SummaryStats sEmergencyCallStats;
+ private static SummaryStats sProxNearStats;
+ private static SummaryStats sProxFarStats;
public static void tracePickupPulse(boolean withinVibrationThreshold) {
if (!ENABLED) return;
@@ -88,6 +90,8 @@
sScreenOnPulsingStats = new SummaryStats();
sScreenOnNotPulsingStats = new SummaryStats();
sEmergencyCallStats = new SummaryStats();
+ sProxNearStats = new SummaryStats();
+ sProxFarStats = new SummaryStats();
log("init");
KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback);
}
@@ -133,6 +137,12 @@
}
}
+ public static void traceProximityResult(boolean near, long millis) {
+ if (!ENABLED) return;
+ log("proximityResult near=" + near + " millis=" + millis);
+ (near ? sProxNearStats : sProxFarStats).append();
+ }
+
public static void dump(PrintWriter pw) {
synchronized (DozeLog.class) {
if (sMessages == null) return;
@@ -154,6 +164,8 @@
sScreenOnPulsingStats.dump(pw, "Screen on (pulsing)");
sScreenOnNotPulsingStats.dump(pw, "Screen on (not pulsing)");
sEmergencyCallStats.dump(pw, "Emergency call");
+ sProxNearStats.dump(pw, "Proximity (near)");
+ sProxFarStats.dump(pw, "Proximity (far)");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 3afdc3d..f8c5e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -25,11 +25,15 @@
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
import android.media.AudioAttributes;
+import android.os.Handler;
import android.os.PowerManager;
+import android.os.SystemClock;
import android.os.Vibrator;
import android.service.dreams.DreamService;
import android.util.Log;
@@ -55,6 +59,7 @@
private final String mTag = String.format(TAG + ".%08x", hashCode());
private final Context mContext = this;
private final DozeParameters mDozeParameters = new DozeParameters(mContext);
+ private final Handler mHandler = new Handler();
private DozeHost mHost;
private SensorManager mSensors;
@@ -197,33 +202,49 @@
// Here we need a wakelock to stay awake until the pulse is finished.
mWakeLock.acquire();
mPulsing = true;
- mHost.pulseWhileDozing(new DozeHost.PulseCallback() {
+ final long start = SystemClock.uptimeMillis();
+ new ProximityCheck() {
@Override
- public void onPulseStarted() {
- if (mPulsing && mDreaming) {
- turnDisplayOn();
- }
- }
-
- @Override
- public void onPulseFinished() {
- if (mPulsing && mDreaming) {
+ public void onProximityResult(int result) {
+ // avoid pulsing in pockets
+ final boolean isNear = result == RESULT_NEAR;
+ DozeLog.traceProximityResult(isNear, SystemClock.uptimeMillis() - start);
+ if (isNear) {
mPulsing = false;
- turnDisplayOff();
+ mWakeLock.release();
+ return;
}
- mWakeLock.release(); // needs to be unconditional to balance acquire
+
+ // not in-pocket, continue pulsing
+ mHost.pulseWhileDozing(new DozeHost.PulseCallback() {
+ @Override
+ public void onPulseStarted() {
+ if (mPulsing && mDreaming) {
+ turnDisplayOn();
+ }
+ }
+
+ @Override
+ public void onPulseFinished() {
+ if (mPulsing && mDreaming) {
+ mPulsing = false;
+ turnDisplayOff();
+ }
+ mWakeLock.release(); // needs to be unconditional to balance acquire
+ }
+ });
}
- });
+ }.check();
}
}
private void turnDisplayOff() {
- if (DEBUG) Log.d(TAG, "Display off");
+ if (DEBUG) Log.d(mTag, "Display off");
setDozeScreenState(Display.STATE_OFF);
}
private void turnDisplayOn() {
- if (DEBUG) Log.d(TAG, "Display on");
+ if (DEBUG) Log.d(mTag, "Display on");
setDozeScreenState(mDisplayStateSupported ? Display.STATE_DOZE : Display.STATE_ON);
}
@@ -270,24 +291,24 @@
}
private void resetNotificationResets() {
- if (DEBUG) Log.d(TAG, "resetNotificationResets");
+ if (DEBUG) Log.d(mTag, "resetNotificationResets");
mScheduleResetsRemaining = mDozeParameters.getPulseScheduleResets();
}
private void updateNotificationPulse() {
- if (DEBUG) Log.d(TAG, "updateNotificationPulse");
+ if (DEBUG) Log.d(mTag, "updateNotificationPulse");
if (!mDozeParameters.getPulseOnNotifications()) return;
if (mScheduleResetsRemaining <= 0) {
- if (DEBUG) Log.d(TAG, "No more schedule resets remaining");
+ if (DEBUG) Log.d(mTag, "No more schedule resets remaining");
return;
}
final long now = System.currentTimeMillis();
if ((now - mNotificationPulseTime) < mDozeParameters.getPulseDuration()) {
- if (DEBUG) Log.d(TAG, "Recently updated, not resetting schedule");
+ if (DEBUG) Log.d(mTag, "Recently updated, not resetting schedule");
return;
}
mScheduleResetsRemaining--;
- if (DEBUG) Log.d(TAG, "mScheduleResetsRemaining = " + mScheduleResetsRemaining);
+ if (DEBUG) Log.d(mTag, "mScheduleResetsRemaining = " + mScheduleResetsRemaining);
mNotificationPulseTime = now;
rescheduleNotificationPulse(true /*predicate*/);
}
@@ -302,31 +323,31 @@
}
private void rescheduleNotificationPulse(boolean predicate) {
- if (DEBUG) Log.d(TAG, "rescheduleNotificationPulse predicate=" + predicate);
+ if (DEBUG) Log.d(mTag, "rescheduleNotificationPulse predicate=" + predicate);
final PendingIntent notificationPulseIntent = notificationPulseIntent(0);
mAlarmManager.cancel(notificationPulseIntent);
if (!predicate) {
- if (DEBUG) Log.d(TAG, " don't reschedule: predicate is false");
+ if (DEBUG) Log.d(mTag, " don't reschedule: predicate is false");
return;
}
final PulseSchedule schedule = mDozeParameters.getPulseSchedule();
if (schedule == null) {
- if (DEBUG) Log.d(TAG, " don't reschedule: schedule is null");
+ if (DEBUG) Log.d(mTag, " don't reschedule: schedule is null");
return;
}
final long now = System.currentTimeMillis();
final long time = schedule.getNextTime(now, mNotificationPulseTime);
if (time <= 0) {
- if (DEBUG) Log.d(TAG, " don't reschedule: time is " + time);
+ if (DEBUG) Log.d(mTag, " don't reschedule: time is " + time);
return;
}
final long delta = time - now;
if (delta <= 0) {
- if (DEBUG) Log.d(TAG, " don't reschedule: delta is " + delta);
+ if (DEBUG) Log.d(mTag, " don't reschedule: delta is " + delta);
return;
}
final long instance = time - mNotificationPulseTime;
- if (DEBUG) Log.d(TAG, "Scheduling pulse " + instance + " in " + delta + "ms for "
+ if (DEBUG) Log.d(mTag, "Scheduling pulse " + instance + " in " + delta + "ms for "
+ new Date(time));
mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, time, notificationPulseIntent(instance));
}
@@ -404,7 +425,9 @@
private final boolean mConfigured;
private final boolean mDebugVibrate;
- private boolean mEnabled;
+ private boolean mRequested;
+ private boolean mRegistered;
+ private boolean mDisabled;
public TriggerSensor(int type, boolean configured, boolean debugVibrate) {
mSensor = mSensors.getDefaultSensor(type);
@@ -413,19 +436,34 @@
}
public void setListening(boolean listen) {
+ if (mRequested == listen) return;
+ mRequested = listen;
+ updateListener();
+ }
+
+ public void setDisabled(boolean disabled) {
+ if (mDisabled == disabled) return;
+ mDisabled = disabled;
+ updateListener();
+ }
+
+ private void updateListener() {
if (!mConfigured || mSensor == null) return;
- if (listen) {
- mEnabled = mSensors.requestTriggerSensor(this, mSensor);
- } else if (mEnabled) {
+ if (mRequested && !mDisabled) {
+ mRegistered = mSensors.requestTriggerSensor(this, mSensor);
+ } else if (mRegistered) {
mSensors.cancelTriggerSensor(this, mSensor);
- mEnabled = false;
+ mRegistered = false;
}
}
@Override
public String toString() {
- return new StringBuilder("{mEnabled=").append(mEnabled).append(", mConfigured=")
- .append(mConfigured).append(", mDebugVibrate=").append(mDebugVibrate)
+ return new StringBuilder("{mRegistered=").append(mRegistered)
+ .append(", mRequested=").append(mRequested)
+ .append(", mDisabled=").append(mDisabled)
+ .append(", mConfigured=").append(mConfigured)
+ .append(", mDebugVibrate=").append(mDebugVibrate)
.append(", mSensor=").append(mSensor).append("}").toString();
}
@@ -449,7 +487,8 @@
// reset the notification pulse schedule, but only if we think we were not triggered
// by a notification-related vibration
- final long timeSinceNotification = System.currentTimeMillis() - mNotificationPulseTime;
+ final long timeSinceNotification = System.currentTimeMillis()
+ - mNotificationPulseTime;
final boolean withinVibrationThreshold =
timeSinceNotification < mDozeParameters.getPickupVibrationThreshold();
if (withinVibrationThreshold) {
@@ -465,4 +504,73 @@
}
}
}
+
+ private abstract class ProximityCheck implements SensorEventListener, Runnable {
+ private static final int TIMEOUT_DELAY_MS = 500;
+
+ protected static final int RESULT_UNKNOWN = 0;
+ protected static final int RESULT_NEAR = 1;
+ protected static final int RESULT_FAR = 2;
+
+ private final String mTag = DozeService.this.mTag + ".ProximityCheck";
+
+ private boolean mRegistered;
+ private boolean mFinished;
+ private float mMaxRange;
+
+ abstract public void onProximityResult(int result);
+
+ public void check() {
+ if (mFinished || mRegistered) return;
+ final Sensor sensor = mSensors.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ if (sensor == null) {
+ if (DEBUG) Log.d(mTag, "No sensor found");
+ finishWithResult(RESULT_UNKNOWN);
+ return;
+ }
+ // the pickup sensor interferes with the prox event, disable it until we have a result
+ mPickupSensor.setDisabled(true);
+
+ mMaxRange = sensor.getMaximumRange();
+ mSensors.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0, mHandler);
+ mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
+ mRegistered = true;
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (event.values.length == 0) {
+ if (DEBUG) Log.d(mTag, "Event has no values!");
+ finishWithResult(RESULT_UNKNOWN);
+ } else {
+ if (DEBUG) Log.d(mTag, "Event: value=" + event.values[0] + " max=" + mMaxRange);
+ final boolean isNear = event.values[0] < mMaxRange;
+ finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
+ }
+ }
+
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(mTag, "No event received before timeout");
+ finishWithResult(RESULT_UNKNOWN);
+ }
+
+ private void finishWithResult(int result) {
+ if (mFinished) return;
+ if (mRegistered) {
+ mHandler.removeCallbacks(this);
+ mSensors.unregisterListener(this);
+ // we're done - reenable the pickup sensor
+ mPickupSensor.setDisabled(false);
+ mRegistered = false;
+ }
+ onProximityResult(result);
+ mFinished = true;
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // noop
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 0b83878..e99b4c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -146,6 +146,7 @@
mDetailAdapter.updateItems();
}
});
+ refreshState();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 9984fca..ce99cc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -66,7 +66,8 @@
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- state.visible = mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed();
+ state.visible = mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed()
+ && !mController.isProvisioningNeeded();
state.label = mContext.getString(R.string.quick_settings_hotspot_label);
state.value = mController.isHotspotEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 582d165..0825aa3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -17,9 +17,11 @@
package com.android.systemui.statusbar;
import android.content.Context;
+import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Interpolator;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -31,6 +33,12 @@
}
@Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ ((TextView) findViewById(R.id.no_notifications)).setText(R.string.empty_shade_text);
+ }
+
+ @Override
protected View findContentView() {
return findViewById(R.id.no_notifications);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index 7ca91a5..0863c86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -22,6 +22,7 @@
boolean isHotspotEnabled();
boolean isHotspotSupported();
void setHotspotEnabled(boolean enabled);
+ boolean isProvisioningNeeded();
public interface Callback {
void onHotspotChanged(boolean enabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index dd706bb9..63c1100 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -24,6 +24,7 @@
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -74,6 +75,20 @@
}
@Override
+ public boolean isProvisioningNeeded() {
+ // Keep in sync with other usage of config_mobile_hotspot_provision_app.
+ // TetherSettings#isProvisioningNeeded and
+ // ConnectivityManager#enforceTetherChangePermission
+ String[] provisionApp = mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_mobile_hotspot_provision_app);
+ if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
+ || provisionApp == null) {
+ return false;
+ }
+ return (provisionApp.length == 2);
+ }
+
+ @Override
public void setHotspotEnabled(boolean enabled) {
final ContentResolver cr = mContext.getContentResolver();
// This needs to be kept up to date with Settings (WifiApEnabler.setSoftapEnabled)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index bbe6622..eb808c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -571,7 +571,7 @@
cancel();
} else {
dismiss();
- UserInfo user = mUserManager.createUser(
+ UserInfo user = mUserManager.createSecondaryUser(
mContext.getString(R.string.user_new_user_name), 0 /* flags */);
if (user == null) {
// Couldn't create user, most likely because there are too many, but we haven't
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 86cfdb9..fca13f8 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2207,9 +2207,15 @@
private List<ResolveInfo> queryIntentReceivers(Intent intent, int userId) {
final long identity = Binder.clearCallingIdentity();
try {
+ int flags = PackageManager.GET_META_DATA;
+
+ // Widgets referencing shared libraries need to have their
+ // dependencies loaded.
+ flags |= PackageManager.GET_SHARED_LIBRARY_FILES;
+
return mPackageManager.queryIntentReceivers(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.GET_META_DATA, userId);
+ flags, userId);
} catch (RemoteException re) {
return Collections.emptyList();
} finally {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index d5858a5..9db3fba 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -22,7 +22,6 @@
import static com.android.internal.util.XmlUtils.readLongAttribute;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.readUriAttribute;
-import static com.android.internal.util.XmlUtils.writeBitmapAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
@@ -47,6 +46,8 @@
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -69,7 +70,6 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.ExceptionUtils;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -125,6 +125,7 @@
private static final String ATTR_INSTALL_LOCATION = "installLocation";
private static final String ATTR_SIZE_BYTES = "sizeBytes";
private static final String ATTR_APP_PACKAGE_NAME = "appPackageName";
+ @Deprecated
private static final String ATTR_APP_ICON = "appIcon";
private static final String ATTR_APP_LABEL = "appLabel";
private static final String ATTR_ORIGINATING_URI = "originatingUri";
@@ -149,10 +150,16 @@
private final Callbacks mCallbacks;
/**
- * File storing persisted {@link #mSessions}.
+ * File storing persisted {@link #mSessions} metadata.
*/
private final AtomicFile mSessionsFile;
+ /**
+ * Directory storing persisted {@link #mSessions} metadata which is too
+ * heavy to store directly in {@link #mSessionsFile}.
+ */
+ private final File mSessionsDir;
+
private final InternalCallback mInternalCallback = new InternalCallback();
/**
@@ -195,26 +202,38 @@
mSessionsFile = new AtomicFile(
new File(Environment.getSystemSecureDirectory(), "install_sessions.xml"));
+ mSessionsDir = new File(Environment.getSystemSecureDirectory(), "install_sessions");
+ mSessionsDir.mkdirs();
synchronized (mSessions) {
readSessionsLocked();
- final ArraySet<File> unclaimed = Sets.newArraySet(mStagingDir.listFiles(sStageFilter));
+ final ArraySet<File> unclaimedStages = Sets.newArraySet(
+ mStagingDir.listFiles(sStageFilter));
+ final ArraySet<File> unclaimedIcons = Sets.newArraySet(
+ mSessionsDir.listFiles());
- // Ignore stages claimed by active sessions
+ // Ignore stages and icons claimed by active sessions
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- unclaimed.remove(session.stageDir);
+ unclaimedStages.remove(session.stageDir);
+ unclaimedIcons.remove(buildAppIconFile(session.sessionId));
}
// Clean up orphaned staging directories
- for (File stage : unclaimed) {
+ for (File stage : unclaimedStages) {
Slog.w(TAG, "Deleting orphan stage " + stage);
if (stage.isDirectory()) {
FileUtils.deleteContents(stage);
}
stage.delete();
}
+
+ // Clean up orphaned icons
+ for (File icon : unclaimedIcons) {
+ Slog.w(TAG, "Deleting orphan icon " + icon);
+ icon.delete();
+ }
}
}
@@ -359,6 +378,12 @@
params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
+ final File appIconFile = buildAppIconFile(sessionId);
+ if (appIconFile.exists()) {
+ params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
+ params.appIconLastModified = appIconFile.lastModified();
+ }
+
return new PackageInstallerSession(mInternalCallback, mContext, mPm,
mInstallThread.getLooper(), sessionId, userId, installerPackageName, installerUid,
params, createdMillis, stageDir, stageCid, prepared, sealed);
@@ -418,15 +443,38 @@
writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes);
writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
- writeBitmapAttribute(out, ATTR_APP_ICON, params.appIcon);
writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
+ // Persist app icon if changed since last written
+ final File appIconFile = buildAppIconFile(session.sessionId);
+ if (params.appIcon == null && appIconFile.exists()) {
+ appIconFile.delete();
+ } else if (params.appIcon != null
+ && appIconFile.lastModified() != params.appIconLastModified) {
+ if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile);
+ FileOutputStream os = null;
+ try {
+ os = new FileOutputStream(appIconFile);
+ params.appIcon.compress(CompressFormat.PNG, 90, os);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage());
+ } finally {
+ IoUtils.closeQuietly(os);
+ }
+
+ params.appIconLastModified = appIconFile.lastModified();
+ }
+
out.endTag(null, TAG_SESSION);
}
+ private File buildAppIconFile(int sessionId) {
+ return new File(mSessionsDir, "app_icon." + sessionId + ".png");
+ }
+
private void writeSessionsAsync() {
IoThread.getHandler().post(new Runnable() {
@Override
@@ -548,7 +596,21 @@
if (session == null || !isCallingUidOwner(session)) {
throw new SecurityException("Caller has no access to session " + sessionId);
}
+
+ // Defensively resize giant app icons
+ if (appIcon != null) {
+ final ActivityManager am = (ActivityManager) mContext.getSystemService(
+ Context.ACTIVITY_SERVICE);
+ final int iconSize = am.getLauncherLargeIconSize();
+ if ((appIcon.getWidth() > iconSize * 2)
+ || (appIcon.getHeight() > iconSize * 2)) {
+ appIcon = Bitmap.createScaledBitmap(appIcon, iconSize, iconSize, true);
+ }
+ }
+
session.params.appIcon = appIcon;
+ session.params.appIconLastModified = -1;
+
mInternalCallback.onSessionBadgingChanged(session);
}
}
@@ -973,6 +1035,12 @@
synchronized (mSessions) {
mSessions.remove(session.sessionId);
mHistoricalSessions.put(session.sessionId, session);
+
+ final File appIconFile = buildAppIconFile(session.sessionId);
+ if (appIconFile.exists()) {
+ appIconFile.delete();
+ }
+
writeSessionsLocked();
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 26e0db3..0cf22493 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -119,7 +119,7 @@
private static final int MIN_USER_ID = 10;
- private static final int USER_VERSION = 4;
+ private static final int USER_VERSION = 5;
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
@@ -462,6 +462,17 @@
}
}
+ /**
+ * If default guest restrictions haven't been initialized yet, add the basic
+ * restrictions.
+ */
+ private void initDefaultGuestRestrictions() {
+ if (mGuestRestrictions.isEmpty()) {
+ mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
+ writeUserListLocked();
+ }
+ }
+
@Override
public Bundle getDefaultGuestRestrictions() {
checkManageUsersPermission("getDefaultGuestRestrictions");
@@ -693,6 +704,11 @@
userVersion = 4;
}
+ if (userVersion < 5) {
+ initDefaultGuestRestrictions();
+ userVersion = 5;
+ }
+
if (userVersion < USER_VERSION) {
Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to "
+ USER_VERSION);
@@ -715,6 +731,7 @@
mUserRestrictions.append(UserHandle.USER_OWNER, restrictions);
updateUserIdsLocked();
+ initDefaultGuestRestrictions();
writeUserListLocked();
writeUserLocked(primary);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a71161a..f934963 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -81,6 +81,13 @@
public static final int STATE_CONNECTING = 9;
/**
+ * The state of a {@code Call} when the user has initiated a disconnection of the call, but the
+ * call has not yet been disconnected by the underlying {@code ConnectionService}. The next
+ * state of the call is (potentially) {@link #STATE_DISCONNECTED}.
+ */
+ public static final int STATE_DISCONNECTING = 10;
+
+ /**
* The key to retrieve the optional {@code PhoneAccount}s Telecom can bundle with its Call
* extras. Used to pass the phone accounts to display on the front end to the user in order to
* select phone accounts to (for example) place a call.
@@ -828,6 +835,8 @@
return STATE_DISCONNECTED;
case CallState.ABORTED:
return STATE_DISCONNECTED;
+ case CallState.DISCONNECTING:
+ return STATE_DISCONNECTING;
default:
Log.wtf(this, "Unrecognized CallState %s", parcelableCallState);
return STATE_NEW;
diff --git a/telecomm/java/android/telecom/CallState.java b/telecomm/java/android/telecom/CallState.java
index 7690847..bd9223a 100644
--- a/telecomm/java/android/telecom/CallState.java
+++ b/telecomm/java/android/telecom/CallState.java
@@ -100,6 +100,16 @@
*/
public static final int ABORTED = 8;
+ /**
+ * Indicates that the call is in the process of being disconnected and will transition next
+ * to a {@link #DISCONNECTED} state.
+ * <p>
+ * This state is not expected to be communicated from the Telephony layer, but will be reported
+ * to the InCall UI for calls where disconnection has been initiated by the user but the
+ * ConnectionService has confirmed the call as disconnected.
+ */
+ public static final int DISCONNECTING = 9;
+
public static String toString(int callState) {
switch (callState) {
case NEW:
@@ -120,6 +130,8 @@
return "DISCONNECTED";
case ABORTED:
return "ABORTED";
+ case DISCONNECTING:
+ return "DISCONNECTING";
default:
return "UNKNOWN";
}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index efd311e..6eee99d 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -104,12 +104,14 @@
PhoneAccountHandle connectionManagerPhoneAccount,
String id,
ConnectionRequest request,
- boolean isIncoming) {
+ boolean isIncoming,
+ boolean isUnknown) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = connectionManagerPhoneAccount;
args.arg2 = id;
args.arg3 = request;
args.argi1 = isIncoming ? 1 : 0;
+ args.argi2 = isUnknown ? 1 : 0;
mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
}
@@ -221,6 +223,7 @@
final String id = (String) args.arg2;
final ConnectionRequest request = (ConnectionRequest) args.arg3;
final boolean isIncoming = args.argi1 == 1;
+ final boolean isUnknown = args.argi2 == 1;
if (!mAreAccountsInitialized) {
Log.d(this, "Enqueueing pre-init request %s", id);
mPreInitializationConnectionRequests.add(new Runnable() {
@@ -230,7 +233,8 @@
connectionManagerPhoneAccount,
id,
request,
- isIncoming);
+ isIncoming,
+ isUnknown);
}
});
} else {
@@ -238,7 +242,8 @@
connectionManagerPhoneAccount,
id,
request,
- isIncoming);
+ isIncoming,
+ isUnknown);
}
} finally {
args.recycle();
@@ -523,12 +528,14 @@
final PhoneAccountHandle callManagerAccount,
final String callId,
final ConnectionRequest request,
- boolean isIncoming) {
+ boolean isIncoming,
+ boolean isUnknown) {
Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
- "isIncoming: %b", callManagerAccount, callId, request, isIncoming);
+ "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request, isIncoming,
+ isUnknown);
- Connection connection = isIncoming
- ? onCreateIncomingConnection(callManagerAccount, request)
+ Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
+ : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
: onCreateOutgoingConnection(callManagerAccount, request);
Log.d(this, "createConnection, connection: %s", connection);
if (connection == null) {
@@ -873,6 +880,23 @@
}
/**
+ * Create a {@code Connection} for a new unknown call. An unknown call is a call originating
+ * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
+ * call created using
+ * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
+ *
+ * @param connectionManagerPhoneAccount
+ * @param request
+ * @return
+ *
+ * @hide
+ */
+ public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
+ return null;
+ }
+
+ /**
* Conference two specified connections. Invoked when the user has made a request to merge the
* specified connections into a conference call. In response, the connection service should
* create an instance of {@link Conference} and pass it into {@link #addConference}.
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 328dc86..de1dc17 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -348,7 +348,8 @@
connectionManagerPhoneAccount,
id,
newRequest,
- isIncoming);
+ isIncoming,
+ false /* isUnknownCall */);
connection.registerCallback(new RemoteConnection.Callback() {
@Override
public void onDestroyed(RemoteConnection connection) {
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index b4d429a..f3358f8 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -51,6 +51,13 @@
public static final String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
/**
+ * Similar to {@link #ACTION_INCOMING_CALL}, but is used only by Telephony to add a new
+ * sim-initiated MO call for carrier testing.
+ * @hide
+ */
+ public static final String ACTION_NEW_UNKNOWN_CALL = "android.telecom.action.NEW_UNKNOWN_CALL";
+
+ /**
* The {@link android.content.Intent} action used to configure a
* {@link android.telecom.ConnectionService}.
* @hide
@@ -125,6 +132,12 @@
"android.telecom.extra.OUTGOING_CALL_EXTRAS";
/**
+ * @hide
+ */
+ public static final String EXTRA_UNKNOWN_CALL_HANDLE =
+ "android.telecom.extra.UNKNOWN_CALL_HANDLE";
+
+ /**
* Optional extra for {@link android.telephony.TelephonyManager#ACTION_PHONE_STATE_CHANGED}
* containing the disconnect code.
*/
@@ -815,6 +828,29 @@
}
/**
+ * Registers a new unknown call with Telecom. This can only be called by the system Telephony
+ * service. This is invoked when Telephony detects a new unknown connection that was neither
+ * a new incoming call, nor an user-initiated outgoing call.
+ *
+ * @param phoneAccount A {@link PhoneAccountHandle} registered with
+ * {@link #registerPhoneAccount}.
+ * @param extras A bundle that will be passed through to
+ * {@link ConnectionService#onCreateIncomingConnection}.
+ * @hide
+ */
+ @SystemApi
+ public void addNewUnknownCall(PhoneAccountHandle phoneAccount, Bundle extras) {
+ try {
+ if (isServiceConnected()) {
+ getTelecomService().addNewUnknownCall(
+ phoneAccount, extras == null ? new Bundle() : extras);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException adding a new unknown call: " + phoneAccount, e);
+ }
+ }
+
+ /**
* Processes the specified dial string as an MMI code.
* MMI codes are any sequence of characters entered into the dialpad that contain a "*" or "#".
* Some of these sequences launch special behavior through handled by Telephony.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 1059da37..339a982 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -39,7 +39,8 @@
in PhoneAccountHandle connectionManagerPhoneAccount,
String callId,
in ConnectionRequest request,
- boolean isIncoming);
+ boolean isIncoming,
+ boolean isUnknown);
void abort(String callId);
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index feb09d5..f1cf885 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -177,4 +177,9 @@
* @see TelecomServiceImpl#addNewIncomingCall
*/
void addNewIncomingCall(in PhoneAccountHandle phoneAccount, in Bundle extras);
+
+ /**
+ * @see TelecomServiceImpl#addNewUnknownCall
+ */
+ void addNewUnknownCall(in PhoneAccountHandle phoneAccount, in Bundle extras);
}
diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
index d1c8ef0..ef39a6c 100644
--- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java
@@ -754,6 +754,18 @@
}
}
+ public static boolean isGsmSeptets(char c) {
+ if (sCharsToGsmTables[0].get(c, -1) != -1) {
+ return true;
+ }
+
+ if (sCharsToShiftTables[0].get(c, -1) != -1) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Returns the count of 7-bit GSM alphabet characters needed
* to represent this string, using the specified 7-bit language table
diff --git a/test-runner/src/android/test/ClassPathPackageInfoSource.java b/test-runner/src/android/test/ClassPathPackageInfoSource.java
index 7b10e38..0ffecdb 100644
--- a/test-runner/src/android/test/ClassPathPackageInfoSource.java
+++ b/test-runner/src/android/test/ClassPathPackageInfoSource.java
@@ -87,7 +87,7 @@
// We get errors in the emulator if we don't use the caller's class loader.
topLevelClasses.add(Class.forName(className, false,
(classLoader != null) ? classLoader : CLASS_LOADER));
- } catch (ClassNotFoundException e) {
+ } catch (ClassNotFoundException | NoClassDefFoundError e) {
// Should not happen unless there is a generated class that is not included in
// the .apk.
Log.w("ClassPathPackageInfoSource", "Cannot load class. "
diff --git a/tests/SharedLibrary/client/AndroidManifest.xml b/tests/SharedLibrary/client/AndroidManifest.xml
index a39c754..d1167fa 100644
--- a/tests/SharedLibrary/client/AndroidManifest.xml
+++ b/tests/SharedLibrary/client/AndroidManifest.xml
@@ -25,5 +25,13 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <receiver android:name="DependentAppwidgetProvider">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/dependent_appwidget_info" />
+ </receiver>
</application>
</manifest>
diff --git a/tests/SharedLibrary/client/res/layout/dependent_appwidget.xml b/tests/SharedLibrary/client/res/layout/dependent_appwidget.xml
new file mode 100644
index 0000000..13beafa
--- /dev/null
+++ b/tests/SharedLibrary/client/res/layout/dependent_appwidget.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView android:id="@+id/label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@com.google.android.test.shared_library:string/shared_string"
+ style="@com.google.android.test.shared_library:style/CodeFont"/>
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@com.google.android.test.shared_library:drawable/size_48x48"/>
+</LinearLayout>
diff --git a/tests/SharedLibrary/client/res/xml/dependent_appwidget_info.xml b/tests/SharedLibrary/client/res/xml/dependent_appwidget_info.xml
new file mode 100644
index 0000000..452e010
--- /dev/null
+++ b/tests/SharedLibrary/client/res/xml/dependent_appwidget_info.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:minWidth="40dp"
+ android:minHeight="40dp"
+ android:updatePeriodMillis="0"
+ android:initialLayout="@layout/dependent_appwidget"
+ android:resizeMode="horizontal|vertical" />
diff --git a/tests/SharedLibrary/client/src/com/google/android/test/lib_client/DependentAppwidgetProvider.java b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/DependentAppwidgetProvider.java
new file mode 100644
index 0000000..4e9b26b
--- /dev/null
+++ b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/DependentAppwidgetProvider.java
@@ -0,0 +1,29 @@
+/*
+ * 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 com.google.android.test.lib_client;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class DependentAppwidgetProvider extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ }
+}
diff --git a/tests/SharedLibrary/lib/res/values/public.xml b/tests/SharedLibrary/lib/res/values/public.xml
index 37b1ec9..23d307e 100644
--- a/tests/SharedLibrary/lib/res/values/public.xml
+++ b/tests/SharedLibrary/lib/res/values/public.xml
@@ -13,5 +13,7 @@
<public type="attr" name="zip" id="0x00010005" />
<public type="attr" name="country" id="0x00010006" />
+ <public type="drawable" name="size_48x48" id="0x00030000" />
+
<public type="array" name="animals" id="0x02050000" />
</resources>