diff options
85 files changed, 3222 insertions, 999 deletions
diff --git a/api/17.txt b/api/17.txt index f35a70637fd9..e86de90ab46d 100644 --- a/api/17.txt +++ b/api/17.txt @@ -331,6 +331,7 @@ package android { field public static final int checkboxStyle = 16842860; // 0x101006c field public static final int checked = 16843014; // 0x1010106 field public static final int checkedButton = 16843080; // 0x1010148 + field public static final int checkedTextViewStyle = 16843720; // 0x10103c8 field public static final int childDivider = 16843025; // 0x1010111 field public static final int childIndicator = 16843020; // 0x101010c field public static final int childIndicatorLeft = 16843023; // 0x101010f @@ -1789,6 +1790,7 @@ package android { field public static final int Widget_DeviceDefault_Button_Small = 16974146; // 0x1030142 field public static final int Widget_DeviceDefault_Button_Toggle = 16974148; // 0x1030144 field public static final int Widget_DeviceDefault_CalendarView = 16974190; // 0x103016e + field public static final int Widget_DeviceDefault_CheckedTextView = 16974299; // 0x10301db field public static final int Widget_DeviceDefault_CompoundButton_CheckBox = 16974152; // 0x1030148 field public static final int Widget_DeviceDefault_CompoundButton_RadioButton = 16974169; // 0x1030159 field public static final int Widget_DeviceDefault_CompoundButton_Star = 16974173; // 0x103015d @@ -1822,6 +1824,7 @@ package android { field public static final int Widget_DeviceDefault_Light_Button_Small = 16974198; // 0x1030176 field public static final int Widget_DeviceDefault_Light_Button_Toggle = 16974200; // 0x1030178 field public static final int Widget_DeviceDefault_Light_CalendarView = 16974238; // 0x103019e + field public static final int Widget_DeviceDefault_Light_CheckedTextView = 16974300; // 0x10301dc field public static final int Widget_DeviceDefault_Light_CompoundButton_CheckBox = 16974204; // 0x103017c field public static final int Widget_DeviceDefault_Light_CompoundButton_RadioButton = 16974224; // 0x1030190 field public static final int Widget_DeviceDefault_Light_CompoundButton_Star = 16974228; // 0x1030194 @@ -1907,6 +1910,7 @@ package android { field public static final int Widget_Holo_Button_Small = 16973964; // 0x103008c field public static final int Widget_Holo_Button_Toggle = 16973966; // 0x103008e field public static final int Widget_Holo_CalendarView = 16974060; // 0x10300ec + field public static final int Widget_Holo_CheckedTextView = 16974297; // 0x10301d9 field public static final int Widget_Holo_CompoundButton_CheckBox = 16973969; // 0x1030091 field public static final int Widget_Holo_CompoundButton_RadioButton = 16973986; // 0x10300a2 field public static final int Widget_Holo_CompoundButton_Star = 16973990; // 0x10300a6 @@ -1940,6 +1944,7 @@ package android { field public static final int Widget_Holo_Light_Button_Small = 16974007; // 0x10300b7 field public static final int Widget_Holo_Light_Button_Toggle = 16974009; // 0x10300b9 field public static final int Widget_Holo_Light_CalendarView = 16974061; // 0x10300ed + field public static final int Widget_Holo_Light_CheckedTextView = 16974298; // 0x10301da field public static final int Widget_Holo_Light_CompoundButton_CheckBox = 16974012; // 0x10300bc field public static final int Widget_Holo_Light_CompoundButton_RadioButton = 16974032; // 0x10300d0 field public static final int Widget_Holo_Light_CompoundButton_Star = 16974036; // 0x10300d4 @@ -2673,6 +2678,7 @@ package android.app { method public void invalidateOptionsMenu(); method public boolean isChangingConfigurations(); method public final boolean isChild(); + method public boolean isDestroyed(); method public boolean isFinishing(); method public boolean isTaskRoot(); method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); @@ -3480,6 +3486,7 @@ package android.app { method public abstract int getBackStackEntryCount(); method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String); method public void invalidateOptionsMenu(); + method public abstract boolean isDestroyed(); method public abstract void popBackStack(); method public abstract void popBackStack(java.lang.String, int); method public abstract void popBackStack(int, int); diff --git a/api/current.txt b/api/current.txt index a36727dab0c8..e86de90ab46d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2678,6 +2678,7 @@ package android.app { method public void invalidateOptionsMenu(); method public boolean isChangingConfigurations(); method public final boolean isChild(); + method public boolean isDestroyed(); method public boolean isFinishing(); method public boolean isTaskRoot(); method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); @@ -3485,6 +3486,7 @@ package android.app { method public abstract int getBackStackEntryCount(); method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String); method public void invalidateOptionsMenu(); + method public abstract boolean isDestroyed(); method public abstract void popBackStack(); method public abstract void popBackStack(java.lang.String, int); method public abstract void popBackStack(int, int); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 7606d5e3d215..5dc9da285ee9 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -685,6 +685,7 @@ public class Activity extends ContextThemeWrapper private boolean mStopped; boolean mFinished; boolean mStartedActivity; + private boolean mDestroyed; /** true if the activity is going through a transient pause */ /*package*/ boolean mTemporaryPause = false; /** true if the activity is being destroyed in order to recreate it with a new configuration */ @@ -4089,6 +4090,14 @@ public class Activity extends ContextThemeWrapper } /** + * Returns true if the final {@link #onDestroy()} call has been made + * on the Activity, so this instance is now dead. + */ + public boolean isDestroyed() { + return mDestroyed; + } + + /** * Check to see whether this activity is in the process of being destroyed in order to be * recreated with a new configuration. This is often used in * {@link #onStop} to determine whether the state needs to be cleaned up or will be passed @@ -5258,6 +5267,7 @@ public class Activity extends ContextThemeWrapper } final void performDestroy() { + mDestroyed = true; mWindow.destroy(); mFragments.dispatchDestroy(); onDestroy(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index c41405b318eb..59fa1e00270d 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1703,6 +1703,7 @@ class ContextImpl extends Context { if (packageName.equals("system") || packageName.equals("android")) { final ContextImpl context = new ContextImpl(mMainThread.getSystemContext()); context.mBasePackageName = mBasePackageName; + context.mUser = user; return context; } diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index e98329912470..10ea109edc08 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -318,6 +318,12 @@ public abstract class FragmentManager { public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f); /** + * Returns true if the final {@link Activity#onDestroy() Activity.onDestroy()} + * call has been made on the FragmentManager's Activity, so this instance is now dead. + */ + public abstract boolean isDestroyed(); + + /** * Print the FragmentManager's state into the given stream. * * @param prefix Text to print at the front of each line. @@ -588,6 +594,11 @@ final class FragmentManagerImpl extends FragmentManager { } @Override + public boolean isDestroyed() { + return mDestroyed; + } + + @Override public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("FragmentManager{"); diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 98bd4f57e867..8286686fec61 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -44,12 +44,12 @@ interface IUsbManager /* Sets the default package for a USB device * (or clears it if the package name is null) */ - void setDevicePackage(in UsbDevice device, String packageName); + void setDevicePackage(in UsbDevice device, String packageName, int userId); /* Sets the default package for a USB accessory * (or clears it if the package name is null) */ - void setAccessoryPackage(in UsbAccessory accessory, String packageName); + void setAccessoryPackage(in UsbAccessory accessory, String packageName, int userId); /* Returns true if the caller has permission to access the device. */ boolean hasDevicePermission(in UsbDevice device); @@ -77,10 +77,10 @@ interface IUsbManager void grantAccessoryPermission(in UsbAccessory accessory, int uid); /* Returns true if the USB manager has default preferences or permissions for the package */ - boolean hasDefaults(String packageName); + boolean hasDefaults(String packageName, int userId); /* Clears default preferences and permissions for the package */ - void clearDefaults(String packageName); + void clearDefaults(String packageName, int userId); /* Sets the current USB function. */ void setCurrentFunction(String function, boolean makeDefault); diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 7b16f4daf153..2e38960ed3d2 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -117,4 +117,8 @@ public class BatteryManager { public static final int BATTERY_PLUGGED_USB = 2; /** Power source is wireless. */ public static final int BATTERY_PLUGGED_WIRELESS = 4; + + /** @hide */ + public static final int BATTERY_PLUGGED_ANY = + BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2a8cf21f1c8e..3bbdf3655001 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -911,17 +911,20 @@ public final class Settings { } private static final HashSet<String> MOVED_TO_GLOBAL; + private static final HashSet<String> MOVED_TO_SECURE_THEN_GLOBAL; static { MOVED_TO_GLOBAL = new HashSet<String>(); + MOVED_TO_SECURE_THEN_GLOBAL = new HashSet<String>(); + // these were originally in system but migrated to secure in the past, // so are duplicated in the Secure.* namespace - MOVED_TO_GLOBAL.add(Global.ADB_ENABLED); - MOVED_TO_GLOBAL.add(Global.BLUETOOTH_ON); - MOVED_TO_GLOBAL.add(Global.DATA_ROAMING); - MOVED_TO_GLOBAL.add(Global.DEVICE_PROVISIONED); - MOVED_TO_GLOBAL.add(Global.INSTALL_NON_MARKET_APPS); - MOVED_TO_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED); - MOVED_TO_GLOBAL.add(Global.HTTP_PROXY); + MOVED_TO_SECURE_THEN_GLOBAL.add(Global.ADB_ENABLED); + MOVED_TO_SECURE_THEN_GLOBAL.add(Global.BLUETOOTH_ON); + MOVED_TO_SECURE_THEN_GLOBAL.add(Global.DATA_ROAMING); + MOVED_TO_SECURE_THEN_GLOBAL.add(Global.DEVICE_PROVISIONED); + MOVED_TO_SECURE_THEN_GLOBAL.add(Global.INSTALL_NON_MARKET_APPS); + MOVED_TO_SECURE_THEN_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED); + MOVED_TO_SECURE_THEN_GLOBAL.add(Global.HTTP_PROXY); // these are moving directly from system to global MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_ON); @@ -954,6 +957,17 @@ public final class Settings { MOVED_TO_GLOBAL.add(Settings.Global.ALWAYS_FINISH_ACTIVITIES); } + /** @hide */ + public static void getMovedKeys(HashSet<String> outKeySet) { + outKeySet.addAll(MOVED_TO_GLOBAL); + outKeySet.addAll(MOVED_TO_SECURE_THEN_GLOBAL); + } + + /** @hide */ + public static void getNonLegacyMovedKeys(HashSet<String> outKeySet) { + outKeySet.addAll(MOVED_TO_GLOBAL); + } + /** * Look up a name in the database. * @param resolver to access the database with @@ -972,7 +986,7 @@ public final class Settings { + " to android.provider.Settings.Secure, returning read-only value."); return Secure.getStringForUser(resolver, name, userHandle); } - if (MOVED_TO_GLOBAL.contains(name)) { + if (MOVED_TO_GLOBAL.contains(name) || MOVED_TO_SECURE_THEN_GLOBAL.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Global, returning read-only value."); return Global.getStringForUser(resolver, name, userHandle); @@ -999,7 +1013,7 @@ public final class Settings { + " to android.provider.Settings.Secure, value is unchanged."); return false; } - if (MOVED_TO_GLOBAL.contains(name)) { + if (MOVED_TO_GLOBAL.contains(name) || MOVED_TO_SECURE_THEN_GLOBAL.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Global, value is unchanged."); return false; @@ -1019,7 +1033,7 @@ public final class Settings { + " to android.provider.Settings.Secure, returning Secure URI."); return Secure.getUriFor(Secure.CONTENT_URI, name); } - if (MOVED_TO_GLOBAL.contains(name)) { + if (MOVED_TO_GLOBAL.contains(name) || MOVED_TO_SECURE_THEN_GLOBAL.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Global, returning read-only global URI."); return Global.getUriFor(Global.CONTENT_URI, name); @@ -2257,7 +2271,7 @@ public final class Settings { * @hide */ public static final String[] SETTINGS_TO_BACKUP = { - STAY_ON_WHILE_PLUGGED_IN, + STAY_ON_WHILE_PLUGGED_IN, // moved to global WIFI_USE_STATIC_IP, WIFI_STATIC_IP, WIFI_STATIC_GATEWAY, @@ -2272,7 +2286,7 @@ public final class Settings { SCREEN_BRIGHTNESS_MODE, SCREEN_AUTO_BRIGHTNESS_ADJ, VIBRATE_INPUT_DEVICES, - MODE_RINGER, + MODE_RINGER, // moved to global MODE_RINGER_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED, VOLUME_VOICE, @@ -2293,20 +2307,18 @@ public final class Settings { TEXT_AUTO_CAPS, TEXT_AUTO_PUNCTUATE, TEXT_SHOW_PASSWORD, - AUTO_TIME, - AUTO_TIME_ZONE, + AUTO_TIME, // moved to global + AUTO_TIME_ZONE, // moved to global TIME_12_24, DATE_FORMAT, DTMF_TONE_WHEN_DIALING, DTMF_TONE_TYPE_WHEN_DIALING, - Global.EMERGENCY_TONE, - Global.CALL_AUTO_RETRY, HEARING_AID, TTY_MODE, SOUND_EFFECTS_ENABLED, HAPTIC_FEEDBACK_ENABLED, - POWER_SOUNDS_ENABLED, - DOCK_SOUNDS_ENABLED, + POWER_SOUNDS_ENABLED, // moved to global + DOCK_SOUNDS_ENABLED, // moved to global LOCKSCREEN_SOUNDS_ENABLED, SHOW_WEB_SUGGESTIONS, NOTIFICATION_LIGHT_PULSE, @@ -2702,6 +2714,11 @@ public final class Settings { MOVED_TO_GLOBAL.add(Settings.Global.PREFERRED_CDMA_SUBSCRIPTION); } + /** @hide */ + public static void getMovedKeys(HashSet<String> outKeySet) { + outKeySet.addAll(MOVED_TO_GLOBAL); + } + /** * Look up a name in the database. * @param resolver to access the database with @@ -3993,12 +4010,11 @@ public final class Settings { * @hide */ public static final String[] SETTINGS_TO_BACKUP = { - ADB_ENABLED, BUGREPORT_IN_POWER_MENU, ALLOW_MOCK_LOCATION, PARENTAL_CONTROL_ENABLED, PARENTAL_CONTROL_REDIRECT_URL, - USB_MASS_STORAGE_ENABLED, + USB_MASS_STORAGE_ENABLED, // moved to global ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE, @@ -4017,9 +4033,9 @@ public final class Settings { TTS_DEFAULT_COUNTRY, TTS_ENABLED_PLUGINS, TTS_DEFAULT_LOCALE, - WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, - WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, - WIFI_NUM_OPEN_NETWORKS_KEPT, + WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, // moved to global + WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, // moved to global + WIFI_NUM_OPEN_NETWORKS_KEPT, // moved to global MOUNT_PLAY_NOTIFICATION_SND, MOUNT_UMS_AUTOSTART, MOUNT_UMS_PROMPT, @@ -5251,6 +5267,38 @@ public final class Settings { public static final String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities"; + /** + * Settings to backup. This is here so that it's in the same place as the settings + * keys and easy to update. + * + * These keys may be mentioned in the SETTINGS_TO_BACKUP arrays in System + * and Secure as well. This is because those tables drive both backup and + * restore, and restore needs to properly whitelist keys that used to live + * in those namespaces. The keys will only actually be backed up / restored + * if they are also mentioned in this table (Global.SETTINGS_TO_BACKUP). + * + * NOTE: Settings are backed up and restored in the order they appear + * in this array. If you have one setting depending on another, + * make sure that they are ordered appropriately. + * + * @hide + */ + public static final String[] SETTINGS_TO_BACKUP = { + STAY_ON_WHILE_PLUGGED_IN, + MODE_RINGER, + AUTO_TIME, + AUTO_TIME_ZONE, + POWER_SOUNDS_ENABLED, + DOCK_SOUNDS_ENABLED, + USB_MASS_STORAGE_ENABLED, + ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, + WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, + WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, + WIFI_NUM_OPEN_NETWORKS_KEPT, + EMERGENCY_TONE, + CALL_AUTO_RETRY, + }; + // Populated lazily, guarded by class object: private static NameValueCache sNameValueCache = new NameValueCache( SYS_PROP_SETTING_VERSION, diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index 4873860d5934..a74e438c07f4 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -19,9 +19,6 @@ package android.view; import android.content.Context; import android.os.SystemClock; import android.util.FloatMath; -import android.util.Log; - -import java.util.Arrays; /** * Detects scaling transformation gestures using the supplied {@link MotionEvent}s. @@ -143,11 +140,16 @@ public class ScaleGestureDetector { private int mSpanSlop; private int mMinSpan; - private float[] mTouchHistoryLastAccepted; - private int[] mTouchHistoryDirection; - private long[] mTouchHistoryLastAcceptedTime; + // Bounds for recently seen values + private float mTouchUpper; + private float mTouchLower; + private float mTouchHistoryLastAccepted; + private int mTouchHistoryDirection; + private long mTouchHistoryLastAcceptedTime; + private int mTouchMinMajor; private static final long TOUCH_STABILIZE_TIME = 128; // ms + private static final int TOUCH_MIN_MAJOR = 48; // dp /** * Consistency verifier for debugging purposes. @@ -160,6 +162,8 @@ public class ScaleGestureDetector { mContext = context; mListener = listener; mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2; + mTouchMinMajor = + (int) (context.getResources().getDisplayMetrics().density * TOUCH_MIN_MAJOR + 0.5f); mMinSpan = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.config_minScalingSpan); } @@ -172,81 +176,55 @@ public class ScaleGestureDetector { private void addTouchHistory(MotionEvent ev) { final long currentTime = SystemClock.uptimeMillis(); final int count = ev.getPointerCount(); + boolean accept = currentTime - mTouchHistoryLastAcceptedTime >= TOUCH_STABILIZE_TIME; + float total = 0; + int sampleCount = 0; for (int i = 0; i < count; i++) { - final int id = ev.getPointerId(i); - ensureTouchHistorySize(id); - - final boolean hasLastAccepted = !Float.isNaN(mTouchHistoryLastAccepted[id]); - boolean accept = true; + final boolean hasLastAccepted = !Float.isNaN(mTouchHistoryLastAccepted); final int historySize = ev.getHistorySize(); - for (int h = 0; h < historySize + 1; h++) { - final float major; - final float minor; + final int pointerSampleCount = historySize + 1; + for (int h = 0; h < pointerSampleCount; h++) { + float major; if (h < historySize) { major = ev.getHistoricalTouchMajor(i, h); - minor = ev.getHistoricalTouchMinor(i, h); } else { major = ev.getTouchMajor(i); - minor = ev.getTouchMinor(i); } - final float avg = (major + minor) / 2; + if (major < mTouchMinMajor) major = mTouchMinMajor; + total += major; + + if (Float.isNaN(mTouchUpper) || major > mTouchUpper) { + mTouchUpper = major; + } + if (Float.isNaN(mTouchLower) || major < mTouchLower) { + mTouchLower = major; + } if (hasLastAccepted) { - final int directionSig = (int) Math.signum(avg - mTouchHistoryLastAccepted[id]); - if (directionSig != mTouchHistoryDirection[id] || - (directionSig == 0 && mTouchHistoryDirection[id] == 0)) { - mTouchHistoryDirection[id] = directionSig; + final int directionSig = (int) Math.signum(major - mTouchHistoryLastAccepted); + if (directionSig != mTouchHistoryDirection || + (directionSig == 0 && mTouchHistoryDirection == 0)) { + mTouchHistoryDirection = directionSig; final long time = h < historySize ? ev.getHistoricalEventTime(h) : ev.getEventTime(); - mTouchHistoryLastAcceptedTime[id] = time; - accept = false; - } - if (currentTime - mTouchHistoryLastAcceptedTime[id] < TOUCH_STABILIZE_TIME) { + mTouchHistoryLastAcceptedTime = time; accept = false; } } } - - if (accept) { - float newAccepted = (ev.getTouchMajor(i) + ev.getTouchMinor(i)) / 2; - if (hasLastAccepted) { - newAccepted = (mTouchHistoryLastAccepted[id] + newAccepted) / 2; - } - mTouchHistoryLastAccepted[id] = newAccepted; - mTouchHistoryDirection[id] = 0; - mTouchHistoryLastAcceptedTime[id] = ev.getEventTime(); - } + sampleCount += pointerSampleCount; } - } - /** - * Clear out the touch history for a given pointer id. - * @param id pointer id to clear - * @see #addTouchHistory(MotionEvent) - */ - private boolean removeTouchHistoryForId(int id) { - if (id >= mTouchHistoryLastAccepted.length) { - return false; - } - mTouchHistoryLastAccepted[id] = Float.NaN; - mTouchHistoryDirection[id] = 0; - mTouchHistoryLastAcceptedTime[id] = 0; - return true; - } + final float avg = total / sampleCount; - /** - * Get the adjusted combined touchMajor/touchMinor value for a given pointer id - * @param id the pointer id of the data to obtain - * @return the adjusted major/minor value for the point at id - * @see #addTouchHistory(MotionEvent) - */ - private float getAdjustedTouchHistory(int id) { - if (id >= mTouchHistoryLastAccepted.length) { - Log.e(TAG, "Error retrieving adjusted touch history for id=" + id + - " - incomplete event stream?"); - return 0; + if (accept) { + float newAccepted = (mTouchUpper + mTouchLower + avg) / 3; + mTouchUpper = (mTouchUpper + newAccepted) / 2; + mTouchLower = (mTouchLower + newAccepted) / 2; + mTouchHistoryLastAccepted = newAccepted; + mTouchHistoryDirection = 0; + mTouchHistoryLastAcceptedTime = ev.getEventTime(); } - return mTouchHistoryLastAccepted[id]; } /** @@ -254,41 +232,11 @@ public class ScaleGestureDetector { * @see #addTouchHistory(MotionEvent) */ private void clearTouchHistory() { - if (mTouchHistoryLastAccepted == null) { - // All three arrays will be null if this is the case; nothing to do. - return; - } - Arrays.fill(mTouchHistoryLastAccepted, Float.NaN); - Arrays.fill(mTouchHistoryDirection, 0); - Arrays.fill(mTouchHistoryLastAcceptedTime, 0); - } - - private void ensureTouchHistorySize(int id) { - final int requiredSize = id + 1; - if (mTouchHistoryLastAccepted == null || mTouchHistoryLastAccepted.length < requiredSize) { - final float[] newLastAccepted = new float[requiredSize]; - final int[] newDirection = new int[requiredSize]; - final long[] newLastAcceptedTime = new long[requiredSize]; - - int oldLength = 0; - if (mTouchHistoryLastAccepted != null) { - System.arraycopy(mTouchHistoryLastAccepted, 0, newLastAccepted, 0, - mTouchHistoryLastAccepted.length); - System.arraycopy(mTouchHistoryDirection, 0, newDirection, 0, - mTouchHistoryDirection.length); - System.arraycopy(mTouchHistoryLastAcceptedTime, 0, newLastAcceptedTime, 0, - mTouchHistoryLastAcceptedTime.length); - oldLength = mTouchHistoryLastAccepted.length; - } - - Arrays.fill(newLastAccepted, oldLength, newLastAccepted.length, Float.NaN); - Arrays.fill(newDirection, oldLength, newDirection.length, 0); - Arrays.fill(newLastAcceptedTime, oldLength, newLastAcceptedTime.length, 0); - - mTouchHistoryLastAccepted = newLastAccepted; - mTouchHistoryDirection = newDirection; - mTouchHistoryLastAcceptedTime = newLastAcceptedTime; - } + mTouchUpper = Float.NaN; + mTouchLower = Float.NaN; + mTouchHistoryLastAccepted = Float.NaN; + mTouchHistoryDirection = 0; + mTouchHistoryLastAcceptedTime = 0; } /** @@ -346,23 +294,16 @@ public class ScaleGestureDetector { final float focusX = sumX / div; final float focusY = sumY / div; - if (pointerUp) { - final int id = event.getPointerId(event.getActionIndex()); - if (!removeTouchHistoryForId(id)) { - Log.e(TAG, "Got ACTION_POINTER_UP for previously unknown id=" + id + - " - incomplete event stream?"); - } - } else { - addTouchHistory(event); - } + + addTouchHistory(event); // Determine average deviation from focal point float devSumX = 0, devSumY = 0; for (int i = 0; i < count; i++) { if (skipIndex == i) continue; - // Average touch major and touch minor and convert the resulting diameter into a radius. - final float touchSize = getAdjustedTouchHistory(event.getPointerId(i)) / 2; + // Convert the resulting diameter into a radius. + final float touchSize = mTouchHistoryLastAccepted / 2; devSumX += Math.abs(event.getX(i) - focusX) + touchSize; devSumY += Math.abs(event.getY(i) - focusY) + touchSize; } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index c67cae636f45..19b825c4fa0f 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -122,6 +122,7 @@ public class Editor { InputMethodState mInputMethodState; DisplayList[] mTextDisplayLists; + int mLastLayoutHeight; boolean mFrozenWithFocus; boolean mSelectionMoved; @@ -1258,6 +1259,16 @@ public class Editor { mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)]; } + // If the height of the layout changes (usually when inserting or deleting a line, + // but could be changes within a span), invalidate everything. We could optimize + // more aggressively (for example, adding offsets to blocks) but it would be more + // complex and we would only get the benefit in some cases. + int layoutHeight = layout.getHeight(); + if (mLastLayoutHeight != layoutHeight) { + invalidateTextDisplayList(); + mLastLayoutHeight = layoutHeight; + } + DynamicLayout dynamicLayout = (DynamicLayout) layout; int[] blockEndLines = dynamicLayout.getBlockEndLines(); int[] blockIndices = dynamicLayout.getBlockIndices(); diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 317baf1552db..925864c9e311 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -30,8 +30,11 @@ import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.PopupWindow.OnDismissListener; /** @@ -978,6 +981,30 @@ public class Spinner extends AbsSpinner implements OnClickListener { super.show(); getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); setSelection(Spinner.this.getSelectedItemPosition()); + + // Make sure we hide if our anchor goes away. + // TODO: This might be appropriate to push all the way down to PopupWindow, + // but it may have other side effects to investigate first. (Text editing handles, etc.) + final ViewTreeObserver vto = getViewTreeObserver(); + if (vto != null) { + final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (!Spinner.this.isVisibleToUser()) { + dismiss(); + } + } + }; + vto.addOnGlobalLayoutListener(layoutListener); + setOnDismissListener(new OnDismissListener() { + @Override public void onDismiss() { + final ViewTreeObserver vto = getViewTreeObserver(); + if (vto != null) { + vto.removeOnGlobalLayoutListener(layoutListener); + } + } + }); + } } } } diff --git a/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.png b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.png Binary files differdeleted file mode 100644 index c30eb1c6983f..000000000000 --- a/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_left.9.png +++ /dev/null diff --git a/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.png b/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.png Binary files differdeleted file mode 100644 index e5d577171884..000000000000 --- a/core/res/res/drawable-nodpi/kg_widget_overscroll_layer_right.9.png +++ /dev/null diff --git a/core/res/res/layout/keyguard_multi_user_avatar.xml b/core/res/res/layout/keyguard_multi_user_avatar.xml index df3ee00819a5..d6a858f16db0 100644 --- a/core/res/res/layout/keyguard_multi_user_avatar.xml +++ b/core/res/res/layout/keyguard_multi_user_avatar.xml @@ -22,6 +22,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="125dp" android:layout_height="125dp" + android:background="#000000" android:gravity="center_horizontal"> <ImageView android:id="@+id/keyguard_user_avatar" @@ -29,12 +30,23 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center"/> - <TextView - android:id="@+id/keyguard_user_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="bottom|right" - android:textSize="12sp" - android:background="#99FFFFFF" - android:textColor="#ff000000"/> -</com.android.internal.policy.impl.keyguard.KeyguardMultiUserAvatar> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <Space + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="0.78" /> + <TextView + android:id="@+id/keyguard_user_name" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="0.22" + android:paddingLeft="6dp" + android:layout_gravity="center_vertical|left" + android:textSize="16sp" + android:textColor="#ffffff" + android:background="#808080" /> + </LinearLayout> +</com.android.internal.policy.impl.keyguard.KeyguardMultiUserAvatar>
\ No newline at end of file diff --git a/core/res/res/layout/keyguard_multi_user_selector_widget.xml b/core/res/res/layout/keyguard_multi_user_selector_widget.xml index c00089cf1478..ad9fdfee4849 100644 --- a/core/res/res/layout/keyguard_multi_user_selector_widget.xml +++ b/core/res/res/layout/keyguard_multi_user_selector_widget.xml @@ -20,6 +20,7 @@ <!-- This is a view that shows general status information in Keyguard. --> <com.android.internal.policy.impl.keyguard.KeyguardWidgetFrame xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_multi_user_selector" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/core/res/res/layout/keyguard_widget_region.xml b/core/res/res/layout/keyguard_widget_region.xml index 7bf06edad0df..01b5551d8876 100644 --- a/core/res/res/layout/keyguard_widget_region.xml +++ b/core/res/res/layout/keyguard_widget_region.xml @@ -38,7 +38,8 @@ android:layout_height="10dp" android:orientation="horizontal" android:paddingLeft="@dimen/kg_widget_pager_horizontal_padding" - android:paddingRight="@dimen/kg_widget_pager_horizontal_padding"> + android:paddingRight="@dimen/kg_widget_pager_horizontal_padding" + android:layout_marginTop="@dimen/kg_runway_lights_top_margin"> <com.android.internal.policy.impl.keyguard.KeyguardGlowStripView android:id="@+id/left_strip" android:paddingTop="@dimen/kg_runway_lights_vertical_padding" diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml index 4e202ac4de33..0c36d4a91d72 100644 --- a/core/res/res/values-sw600dp/dimens.xml +++ b/core/res/res/values-sw600dp/dimens.xml @@ -77,5 +77,13 @@ <!-- Preference fragment padding, sides --> <dimen name="preference_fragment_padding_side">24dp</dimen> <dimen name="preference_screen_header_padding_side">24dip</dimen> + + <!-- Keyguard dimensions --> + <!-- Bottom padding for the widget pager --> + <dimen name="kg_widget_pager_bottom_padding">16dp</dimen> + + <!-- Top margin for the runway lights. We add a negative margin in large + devices to account for the widget pager padding --> + <dimen name="kg_runway_lights_top_margin">-10dp</dimen> </resources> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 07d0d7416239..6a93f3081863 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -115,6 +115,11 @@ <color name="lockscreen_clock_am_pm">#ffffffff</color> <color name="lockscreen_owner_info">#ff9a9a9a</color> + <!-- keyguard overscroll widget pager --> + <color name="kg_multi_user_text_active">#ffffffff</color> + <color name="kg_multi_user_text_inactive">#ff808080</color> + <color name="kg_widget_pager_gradient">#ff33B5E5</color> + <!-- FaceLock --> <color name="facelock_spotlight_mask">#CC000000</color> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 289adf49c46b..10f0d39b6faf 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -286,7 +286,7 @@ <dimen name="kg_runway_lights_height">7dp</dimen> <!-- The height of the runway lights strip --> - <dimen name="kg_runway_lights_vertical_padding">3dp</dimen> + <dimen name="kg_runway_lights_vertical_padding">2dp</dimen> <!-- Horizontal padding for the widget pager --> <dimen name="kg_widget_pager_horizontal_padding">16dp</dimen> @@ -297,6 +297,10 @@ <!-- Bottom padding for the widget pager --> <dimen name="kg_widget_pager_bottom_padding">6dp</dimen> + <!-- Top margin for the runway lights. We add a negative margin in large + devices to account for the widget pager padding --> + <dimen name="kg_runway_lights_top_margin">0dp</dimen> + <!-- Touch slop for the global toggle accessibility gesture --> <dimen name="accessibility_touch_slop">80dip</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8ac0ad5ea652..d423ecda3dcb 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1188,6 +1188,9 @@ <java-symbol type="bool" name="config_reverseDefaultRotation" /> <java-symbol type="bool" name="config_showNavigationBar" /> <java-symbol type="bool" name="target_honeycomb_needs_options_menu" /> + <java-symbol type="color" name="kg_multi_user_text_active" /> + <java-symbol type="color" name="kg_multi_user_text_inactive" /> + <java-symbol type="color" name="kg_widget_pager_gradient" /> <java-symbol type="dimen" name="navigation_bar_height" /> <java-symbol type="dimen" name="navigation_bar_height_landscape" /> <java-symbol type="dimen" name="navigation_bar_width" /> @@ -1213,8 +1216,6 @@ <java-symbol type="drawable" name="magnified_region_frame" /> <java-symbol type="drawable" name="menu_background" /> <java-symbol type="drawable" name="stat_sys_secure" /> - <java-symbol type="drawable" name="kg_widget_overscroll_layer_left" /> - <java-symbol type="drawable" name="kg_widget_overscroll_layer_right" /> <java-symbol type="id" name="action_mode_bar_stub" /> <java-symbol type="id" name="alarm_status" /> <java-symbol type="id" name="backspace" /> @@ -1298,6 +1299,7 @@ <java-symbol type="id" name="kg_widget_region" /> <java-symbol type="id" name="left_strip" /> <java-symbol type="id" name="right_strip" /> + <java-symbol type="id" name="keyguard_multi_user_selector" /> <java-symbol type="integer" name="config_carDockRotation" /> <java-symbol type="integer" name="config_defaultUiModeType" /> @@ -1408,7 +1410,6 @@ <java-symbol type="string" name="kg_password_wrong_pin_code" /> <java-symbol type="string" name="kg_invalid_sim_pin_hint" /> <java-symbol type="string" name="kg_invalid_sim_puk_hint" /> - <java-symbol type="string" name="kg_sim_puk_recovery_hint" /> <java-symbol type="string" name="kg_invalid_puk" /> <java-symbol type="string" name="kg_login_too_many_attempts" /> <java-symbol type="string" name="kg_login_instructions" /> diff --git a/docs/html/distribute/distribute_toc.cs b/docs/html/distribute/distribute_toc.cs index 84103b907721..ad3121c6a994 100644 --- a/docs/html/distribute/distribute_toc.cs +++ b/docs/html/distribute/distribute_toc.cs @@ -28,9 +28,7 @@ <li><a href="<?cs var:toroot ?>distribute/googleplay/publish/preparing.html"> <span class="en">Publishing Checklist</span> </a></li> - <li><a href="<?cs var:toroot ?>distribute/googleplay/strategies/app-quality.html"> - <span class="en">App Quality</span> - </a></li> + </ul> </li> @@ -79,6 +77,26 @@ </ul> </li> + + <li class="nav-section"> + <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/quality/index.html"> + <span class="en">App Quality</span></a> + </div> + <ul> + <li><a href="<?cs var:toroot ?>distribute/googleplay/quality/core.html"> + <span class="en">Core App Quality</span> + </a></li> + <li><a href="<?cs var:toroot ?>distribute/googleplay/quality/tablet.html"> + <span class="en">Tablet App Quality</span> + </a></li> + <li><a href="<?cs var:toroot ?>distribute/googleplay/strategies/app-quality.html"> + <span class="en">Improving App Quality</span> + </a></li> + + </ul> + </li> + + <!-- <li class="nav-section"> <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/after.html"> @@ -92,17 +110,17 @@ </li> --> -<!-- <li class="nav-section"> - <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/strategies/index.html"> - <span class="en">Strategies</span></a> + <div class="nav-section-header"><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/index.html"> + <span class="en">Spotlight</span></a> </div> <ul> - <li><a href="<?cs var:toroot ?>distribute/googleplay/strategies/featuring.html">Featuring</a></li> - <li><a href="<?cs var:toroot ?>distribute/googleplay/strategies/app-quality.html">App Quality</a></li> + <li><a href="<?cs var:toroot ?>distribute/googleplay/spotlight/tablets.html"> + <span class="en">Tablet Stories</span> + </a></li> </ul> </li> ---> + <li class="nav-section"> <div class="nav-section-header empty"> <a href="<?cs var:toroot ?>distribute/open.html"> diff --git a/docs/html/distribute/googleplay/about/monetizing.jd b/docs/html/distribute/googleplay/about/monetizing.jd index 4980edaecbbe..d5c6dfad2df2 100644 --- a/docs/html/distribute/googleplay/about/monetizing.jd +++ b/docs/html/distribute/googleplay/about/monetizing.jd @@ -42,7 +42,7 @@ manages the application download.</p> <h3 id="payment-methods">Convenient payment options</h3> <p>Users can purchase your products on Google Play using several convenient -payment methods—credit cards, Direct Carrier Billing, and Google Play balance.</p> +payment methods—credit cards, Direct Carrier Billing, gift cards, and Google Play balance.</p> <p><span style="font-weight:500">Credit card</span> is the most common method of payment. Users can pay using any credit card that they’ve registered in Google Play. To make it easy for users to get started, @@ -52,8 +52,9 @@ registration is offered as a part of initial device setup process.</p> <div class="sidebox"> <h2>Payment methods on Google Play</h2> <ul> -<li>Credit Card</li> +<li>Credit card</li> <li>Direct Carrier Billing</li> +<li>Gift card</li> <li>Google Play balance (stored value)</li> </ul> </div> diff --git a/docs/html/distribute/googleplay/publish/preparing.jd b/docs/html/distribute/googleplay/publish/preparing.jd index 6ea0f53946ec..463343d2d7e5 100644 --- a/docs/html/distribute/googleplay/publish/preparing.jd +++ b/docs/html/distribute/googleplay/publish/preparing.jd @@ -6,20 +6,21 @@ page.title=Publishing Checklist for Google Play <ol> <li><a href="#process">1. Understand the publishing process</a></li> <li><a href="#policies">2. Understand Google Play policies</a></li> -<li><a href="#rating">3. Determine your content rating</a></li> -<li><a href="#countries">4. Determine country distribution</a></li> -<li><a href="#size">5. Confirm the app's overall size</a></li> -<li><a href="#compatibility">6. Confirm app compatibility ranges</a></li> -<li><a href="#free-priced">7. Decide on free or priced</a></li> -<li><a href="#inapp-billing">8. Consider In-app Billing</a></li> -<li><a href="#pricing">9. Set prices for your apps</a></li> -<li><a href="#localize">10. Start localization</a></li> -<li><a href="#localize">11. Prepare promotional graphics</a></li> -<li><a href="#apk">12. Build the release-ready APK</a></li> -<li><a href="#product-page">13. Complete the product details</a></li> -<li><a href="#badges">14. Use Google Play badges and links to your promotional campaigns</a></li> -<li><a href="#final-checks">15. Final checks and publishing</a></li> -<li><a href="#support">16. Support users after launch</a></li> +<li><a href="#core-app-quality">3. Test for Core App Quality</a></li> +<li><a href="#rating">4. Determine your content rating</a></li> +<li><a href="#countries">5. Determine country distribution</a></li> +<li><a href="#size">6. Confirm the app's overall size</a></li> +<li><a href="#compatibility">7. Confirm app compatibility ranges</a></li> +<li><a href="#free-priced">8. Decide on free or priced</a></li> +<li><a href="#inapp-billing">9. Consider In-app Billing</a></li> +<li><a href="#pricing">10. Set prices for your apps</a></li> +<li><a href="#localize">11. Start localization early</a></li> +<li><a href="#localize">12. Prepare promotional graphics</a></li> +<li><a href="#apk">13. Build the release-ready APK</a></li> +<li><a href="#product-page">14. Complete the product details</a></li> +<li><a href="#badges">15. Use Google Play badges</a></li> +<li><a href="#final-checks">16. Final checks and publishing</a></li> +<li><a href="#support">17. Support users after launch</a></li> </ol> </div></div> @@ -86,7 +87,39 @@ violations, termination of your developer account. </p> </tr> </table> -<h2 id="rating">3. Determine your app's content rating</h2> +<h2 id="core-app-quality">3. Test for Core App Quality</h2> + +<p>Before you publish an app on Google Play, it's important to make sure that +it meets the basic quality expectations for all Android apps, on all of the devices that you +are targeting. You can check your app's quality by setting up a test +environment and testing the app against a short set of <strong>core app quality criteria</strong>. +For complete information, see the <a +href="{@docRoot}distribute/googleplay/quality/core.html">Core App Quality Guidelines</a>. +</p> + +<p>If your app is targeting tablet devices, make sure that it delivers a rich, compelling +experience to your tablet customers. See the <a +href="{@docRoot}distribute/googleplay/quality/tablet.html">Tablet App Quality Checklist</a> +for recommendations on ways to optimize your app for tablets.</p> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a +href="{@docRoot}distribute/googleplay/quality/core.html">Core App Quality +Guidelines</a></strong> — A set of core quality criteria that all Android +apps should meet on all targeted devices.</li> +<li><strong><a +href="{@docRoot}distribute/googleplay/quality/tablet.html">Tablet App Quality +Checklist</a></strong> — A set recommendations for delivering the best +possible experience to tablet users.</li> +</ul> +</td> +</tr> +</table> + +<h2 id="rating">4. Determine your app's content rating</h2> <p>Google Play requires you to set a content rating for your app, which informs Google Play users of its maturity level. Before you publish, you should confirm @@ -115,7 +148,7 @@ are required in your app binary.</p> </tr> </table> -<h2 id="countries">4. Determine country distribution</h2> +<h2 id="countries">5. Determine country distribution</h2> <p>Google Play lets you control what countries and territories your app is distributed to. For widest reach and the largest potential customer base, you @@ -149,7 +182,7 @@ launch target date.</p> </tr> </table> -<h2 id="size">5. Confirm the app's overall size</h2> +<h2 id="size">6. Confirm the app's overall size</h2> <p>The overall size of your app can affect its design and how you publish it on Google Play. Currently, the maximum size for an APK published on Google Play is @@ -180,7 +213,7 @@ creating your release-ready APK.</p> </tr> </table> -<h2 id="compatibility">6. Confirm the app's platform and screen compatibility ranges</h2> +<h2 id="compatibility">7. Confirm the app's platform and screen compatibility ranges</h2> <p>Before publishing, it's important to make sure that your app is designed to run properly on the Android platform versions and device screen sizes that you @@ -217,7 +250,7 @@ charts.</p> </tr> </table> -<h2 id="free-priced">7. Decide whether your app will be free or priced</h2> +<h2 id="free-priced">8. Decide whether your app will be free or priced</h2> <p>On Google Play, you can publish apps as free to download or priced. Free apps can be downloaded by any Android user in Google Play. @@ -249,7 +282,7 @@ you need set up a Checkout Merchant Account before you can publish.</p> </tr> </table> -<h2 id="inapp-billing">8. Consider using In-app Billing</h2> +<h2 id="inapp-billing">9. Consider using In-app Billing</h2> <p>Google Play <a href="{@docRoot}guide/google/play/billing/index.html">In-app Billing</a> lets you sell digital content in your applications. You can use the @@ -275,7 +308,7 @@ before creating your release-ready APK.</p> </tr> </table> -<h2 id="pricing">9. Set prices for your products</h2> +<h2 id="pricing">10. Set prices for your products</h2> <p>If your app is priced or you will sell in-app products, Google Play lets you set prices for your products in a variety of currencies, for users in markets @@ -308,7 +341,7 @@ in all available currencies through the Developer Console.</p> </tr> </table> -<h2 id="localize">10. Start localization</h2> +<h2 id="localize">11. Start localization</h2> <p>With your country targeting in mind, it's a good idea to assess your localization needs and start the work of localizing well in advance of your target @@ -344,7 +377,7 @@ when you upload assets and configure your product details.</p> </tr> </table> -<h2 id="graphics">11. Prepare promotional graphics</h2> +<h2 id="graphics">12. Prepare promotional graphics</h2> <p>When you publish on Google Play, you can supply a variety of high-quality graphic assets to showcase your app or brand. After you publish, these appear on @@ -375,7 +408,7 @@ advance of your target publishing date. </p> </tr> </table> -<h2 id="apk">12. Build and upload the release-ready APK</h2> +<h2 id="apk">13. Build and upload the release-ready APK</h2> <p>When you are satisfied that your app meets your UI, compatibility, and quality requirements, you can build the release-ready version of the app. The @@ -407,7 +440,7 @@ recent version before publishing. </p> </tr> </table> -<h2 id="product-page">13. Complete the app's product details</h2> +<h2 id="product-page">14. Complete the app's product details</h2> <p>On Google Play, your app's product information is shown to users on its product details page, the page that users visit to learn more about your app and @@ -431,6 +464,10 @@ page in the Developer Console. As you collect the information and assets for the page, make sure that you can enter or upload it to the Developer Console, until the page is complete and ready for publishing. </p> +<p>If your app is targeting tablet devices, make sure to include at least one screen +shot of the app running on a tablet, and highlight your app's support for tablets +in the app description, release notes, promotional campaigns, and elsewhere.</p> + <table> <tr> <td><p>Related resources:</p> @@ -444,7 +481,7 @@ the page is complete and ready for publishing. </p> </tr> </table> -<h2 id="badges">14. Use Google Play badges and links in your promotional +<h2 id="badges">15. Use Google Play badges and links in your promotional campaigns</h2> <p>Google Play badges give you an officially branded way of promoting your app @@ -473,7 +510,7 @@ and reviews, or any other channel available.</p> </tr> </table> -<h2 id="final-checks">15. Final checks and publishing</h2> +<h2 id="final-checks">16. Final checks and publishing</h2> <p>When you think you are ready to publish, sign in to the Developer Console and take a few moments for a few final checks:</p> @@ -511,7 +548,7 @@ final checks:</p> </table> -<h2 id="support">16. Support users after launch</h2> +<h2 id="support">17. Support users after launch</h2> <p>After you publish an app or an app update, it's crucial for you to support your customers. Prompt and courteous support can provide a better experience for diff --git a/docs/html/distribute/googleplay/quality/core.jd b/docs/html/distribute/googleplay/quality/core.jd new file mode 100644 index 000000000000..3d017e64b9a5 --- /dev/null +++ b/docs/html/distribute/googleplay/quality/core.jd @@ -0,0 +1,783 @@ +page.title=Core App Quality Guidelines +@jd:body + +<div id="qv-wrapper"><div id="qv"> +<h2>Quality Criteria</h2> + <ol> + <li><a href="#ux">Design and Interaction</a></li> + <li><a href="#fn">Functionality</a></li> + <li><a href="#ps">Performance and Stability</a></li> + <li><a href="#listing">Google Play</a></li> + + </ol> + + <h2>Testing</h2> + <ol> + <li><a href="#test-environment">Setting Up a Test Environment</a></li> + <li><a href="#tests">Test Procedures</a></li> + </ol> + + <h2>You Should Also Read</h2> + <ol> + <li><a href="{@docRoot}distribute/googleplay/quality/tablet.html">Tablet App Quality Checklist</a></li> + <li><a href="{@docRoot}distribute/googleplay/strategies/app-quality.html">Improving App Quality</a></li> + </ol> + + +</div> +</div> + +<p><em>App quality</em> directly influences the long-term success of your app +— in terms of installs, user rating and reviews, engagement, and user +retention. Android users expect high quality design and performance from apps, +even more so if they puchased the apps or purchased their in-app products.</p> + +<p>This document helps you assess basic aspects of quality in your app through a +compact set of <em>core app quality criteria</em> and associated tests. All +Android apps should meet these criteria.</p> + +<p>Before launching your app, make sure to test it against these criteria to +ensure that it functions well on many devices, meets Android standards for +navigation and design, and is prepared for promotional opportunities in the +Google Play Store. Your testing will go well beyond what's described here +— the purpose of this document is to specify the essential characteristics +of basic quality so that you can include them in your test plans.</p> + +<p>If your app is targeting tablet devices, make sure that it delivers a rich, +compelling experience to your tablet customers. See the <a +href="/distribute/googleplay/quality/tablet.html">Tablet App Quality +Checklist</a> for recommendations on ways to optimize your app for tablets.</p> + + +<h2 id="ux">Visual Design and User Interaction</h2> + +<p>These criteria ensure that your app provides standard Android visual design +and interaction patterns where appropriate, for a consistent and intuitive +user experience.</p> + +<table> + <tr> + <th style="width:2px;"> + Area + </th> + <th style="width:54px;"> + ID + </th> + + + <th> + Description + </th> + <th style="width:54px;"> + Tests + </th> + </tr> + <tr id="cg7"> + <td>Standard design</td> + <td> + UX-B1 + </td> + <td> + <p style="margin-bottom:.5em;">App follows <a href="{@docRoot}design/index.html">Android Design</a> guidelines and uses common <a href="{@docRoot}design/patterns/index.html">UI patterns and icons</a>:</p> + <ol style="margin-bottom:.5em;list-style-type:lower-alpha"> + <li>App does not redefine the expected function of a system icon (such as the Back button).</li> + <li>App does not replace a system icon with a completely different icon if it triggers the standard UI behavior. </li> + <li>If the app provides a customized version of a standard system icon, the icon strongly resembles the system icon and triggers the standard system behavior.</li> + <li>App does not redefine or misuse Android UI patterns, such that icons or behaviors could be misleading or confusing to users.</li> + </ol> + </td> + <td><a href="#core">CR-all</a></td> + </tr> + + + <tr> + <td rowspan="3">Navigation</td> + <td id="cn1"> + UX-N1 + </td> + + <td> + <p>App supports standard system <a href="{@docRoot}design/patterns/navigation.html">Back button navigation</a> and does not make use of any custom, on-screen "Back button" prompts.</p> + </td> + <td><a href="#core">CR-3</a></td> + </tr> + <tr> + <td id="cn2"> + UX-N2 + </td> + <td> + <p>All dialogs are dismissable using the Back button.</p> + </td> + <td><a href="#core">CR-3</a></td> + </tr> + + <tr id="cn4"> + <td> + UX-N3 + </td> + <td> + Pressing the Home button at any point navigates to the Home screen of the device. + </td> + <td><a href="#core">CR-1</a></td> + </tr> + </table> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="{@docRoot}design/index.html">Android Design</a></strong> — Overview of design and user experience best practices for Android apps. </li> +<li><strong><a href="{@docRoot}design/patterns/navigation.html">Navigation with Back and Up</a></strong> — Android Design document describing standard navigation patterns and related topics. </li> +<li><strong><a href="{@docRoot}design/patterns/actionbar.html">Action Bar</a></strong> — Android Design document describing how to use the Action Bar. </li> +<li><strong><a href="{@docRoot}design/style/iconography.html">Iconography</a></strong> — Android Design document that shows how to use various types of icons.</li> +</ul> +</td> +</tr> +</table> + +<h2 id="fn">Functionality</h2> + +<p>These criteria ensure that your app provides expected functional behavior with the appropriate level of permissions. </p> + +<table> + <tr> + <th style="width:2px;"> + Area + </th> + <th style="width:54px;"> + ID + </th> + + + <th> + Description + </th> + <th style="width:54px;"> + Tests + </th> + </tr> + + <tr> + <td rowspan="2">Permissions</td> + <td> + FN-P1 + </td> + <td>App requests only the <em>absolute minimum</em> permissions that it needs to support core functionality. + </td> + <td rowspan="2"><a href="#core">CR-11</a></td> + </tr> + <tr> + <td> + FN-P2 + </td> + <td><p style="margin-bottom:.5em;">App does not request permissions to access sensitive data (such as Contacts or the System Log) or services that can cost the user money (such as the Dialer or SMS), unless related to a core capability of the app. + </td> + </tr> + + <tr> + <td>Install location</td> + <td> + FN-L1 + </td> + <td> + <p style="margin-bottom:.5em;">App functions normally when installed on SD card (if supported by app).</p> + + <p style="margin-bottom:.25em;">Supporting installation to SD card is recommended for most large apps (10MB+). See the <a href="{@docRoot}guide/topics/data/install-location.html">App Install Location</a> developer guide for information about which types of apps should support installation to SD card.</p> + </td> + + <td><a href="#SD-1">SD-1</a> + </td> + </tr> + <tr> + <td rowspan="4">Audio</td> + <td id="cg1"> + FN-A1 + </td> + + <td> + Audio does not play when the screen is off, unless this is a core feature (for example, the app is a music player). + </td> + <td><a href="#core">CR-7</a></td> + </tr> + <tr id="cg2"> + <td> + FN-A2 + </td> + <td> + Audio does not <a href="http://android-developers.blogspot.com/2011/11/making-android-games-that-play-nice.html">play behind the lock screen</a>, unless this is a core feature. + </td> + <td><a href="#core">CR-8</a></td> + </tr> + <tr id="cg3"> + <td> + FN-A3 + </td> + <td> + Audio does not play on the home screen or over another app, unless this is a core feature. + </td> + <td><a href="#core">CR-1, <br />CR-2</a></td> + </tr> + <tr id="cg4"> + <td> + FN-A4 + </td> + <td> + Audio resumes when the app returns to the foreground, or indicates to the user that playback is in a paused state. + </td> + <td><a href="#core">CR-1, CR-8</a></td> + </tr> + <tr id="cg5"> + <td rowspan="3">UI and Graphics</td> + <td> + FN-U1 + </td> + +<td> + <p style="margin-bottom:.5em;">App supports both landscape and portrait orientations (if possible).</em></p> + + <p style="margin-bottom:.25em;">Orientations expose largely the same features and actions and preserve functional parity. + Minor changes in content or views are acceptable.</p> + </td> + <td><a href="#core">CR-5</a></td> + </tr> + <tr id="cg5"> + <td> + FN-U2 + </td> + +<td> + <p style="margin-bottom:.5em;">App uses the whole screen in both orientations and does not letterbox to account for orientation changes.</em></p> + <p style="margin-bottom:.25em;">Minor letterboxing to compensate for small variations in screen geometry is acceptable.</p> + </td> + <td><a href="#core">CR-5</a></td> + </tr> + <tr id="cg7"> + <td> + FN-U3 + </td> + <td> + <p style="margin-bottom:.5em;">App correctly handles rapid transitions between display orientations without rendering problems.</p> + </td> + <td><a href="#core">CR-5</a></td> + </tr> + + <tr id="cg8"> + <td rowspan="2">User/app state</td> + <td> + FN-S1 + </td> + <td> + <p style="margin-bottom:.5em;">App should not leave any services running when the app is in the background, unless related to a core capability of the app.</p> + <p style="margin-bottom:.25em;">For example, the app should not leave services running to maintain a network connection for notifications, to maintain a Bluetooth connection, or to keep the GPS powered-on.</p> + </td> + <td><a href="#core">CR-6</a></td> + </tr> + + + <tr id="cn3"> + <td> + FN-S2 + </td> + <td> + <p style="margin-bottom:.5em;">App correctly preserves and restores user or app state.</p> + <p style="margin-bottom:.25em;">App preserves user or app state when leaving the foreground and prevents accidental data loss due to back-navigation and other state changes. When returning to the foreground, the app must restore the preserved state and any significant stateful transaction that was pending, such as changes to editable fields, game progress, menus, videos, and other sections of the app or game.</p> + <ol style="margin-bottom:.25em;list-style-type:lower-alpha"> + <li>When the app is resumed from the Recents app switcher, the app returns the user to the exact state in which it was last used.</li> + <li>When the app is resumed after the device wakes from sleep (locked) state, the app returns the user to the exact state in which it was last used.</li> + <li>When the app is relaunched from Home or All Apps, the app restores the app state as closely as possible to the previous state.</li> + <li>On Back keypresses, the app gives the user the option of saving any app or user state that would otherwise be lost on back-navigation.</li> + </ol> + </td> + <td><a href="#core">CR-1, CR-3, CR-5</a></td> + </tr> + +</table> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="http://android-developers.blogspot.com/2011/11/making-android-games-that-play-nice.html">Making Android Apps that Play Nice</a></strong> — Developer blog post discussing the audio lifecycle and expected audio behaviors for Android apps. </li> +<li><strong><a href="{@docRoot}guide/components/tasks-and-back-stack.html">Tasks and Back Stack</a></strong> — Developer guide describing how to implement back-navigation.</li> +<li><strong><a href="{@docRoot}training/basics/activity-lifecycle/recreating.html">Recreating an Activity</a></strong> — Android Training class the shows how to preserve and restore app state.</li> + +</ul> +</td> +</tr> +</table> + + +<h2 id="ps">Performance and Stability</h2> + +<p>To ensure a high user rating, your app needs to perform well and stay +responsive on all of the devices and form factors and screens that it is +targeting. These criteria ensure that the app provides the basic performance, +stability, and responsiveness expected by users.</p> + +<table> + <tr> + <th style="width:2px;"> + Area + </th> + <th style="width:54px;"> + ID + </th> + <th> + Description + </th> + <th style="width:54px;"> + Tests + </th> + </tr> + + <tr id="cg9"> + <td>Stability</td> + <td> + PS-S1 + </td> + <td> + App does not crash, force close, freeze, or otherwise function abnormally on any targeted device. + </td> + <td><a href="#core">CR-all</a>, <a href="#SD-1">SD-1</a>, <a href="#HA-1">HA-1</a></td> + </tr> + + <tr> + <td rowspan="2">Performance</td> + <td id="cp1"> + PS-P1 + </td> + <td> + App loads quickly or provides onscreen feedback to the user (a progress indicator or similar cue) if the app + takes longer than two seconds to load. + </td> + <td> + <a href="#core">CR-all</a>, <a href="#SD-1">SD-1</a> + </td> + </tr> + <tr id="cp2"> + + <td> + PS-P2 + </td> + <td> + With StrictMode enabled (see <a href="#strictmode">StrictMode Testing</a>, below), no red flashes (performance warnings from StrictMode) are visible when exercising the app, including + during game play, animations and UI transitions, and any other part of the app. + </td> + <td> + <a href="#PM-1">PM-1</a> + </td> + </tr> + <tr id="cp3"> + <td>Media</td> + <td> + PS-M1 + </td> + <td> + Music and video playback is smooth, without crackle, stutter, or other artifacts, during normal app usage and load. + </td> + <td> + <a href="#core">CR-all</a>, <a href="#SD-1">SD-1</a>, <a href="#HA-1">HA-1</a> + </td> + </tr> + +<tr id="cp4"> + <td rowspan="2">Visual quality</td> + <td> + PS-V1 + </td> + <td> + <p style="margin-bottom:.5em;">App displays graphics, text, images, and other UI elements without noticeable distortion, blurring, or pixelation.</p> + + <ol style="margin-bottom:.5em;list-style-type:lower-alpha"> + <li>App provides high-quality graphics for all targeted screen sizes and form factors, including for <a href="{@docRoot}distribute/googleplay/quality/tablet.html">larger-screen devices such as tablets</a>.</li> + <li>No aliasing at the edges of menus, buttons, and other UI elements.</li> + </ol> + </td> + <td rowspan="2"><a href="#core">CR-all</a></td> + </tr> + + <tr id="cp5"> + <td> + PS-V2 + </td> + <td> + <p style="margin-bottom:.5em;">App displays text and text blocks in an acceptable manner. </p> + + <ol style="margin-bottom:.5em;list-style-type:lower-alpha"> + <li>Composition is acceptable in all supported form factors, including for larger-screen devices such as tablets.</li> + <li>No cut-off letters or words.</li> + <li>No improper word wraps within buttons or icons.</li> + <li>Sufficient spacing between text and surrounding elements.</li> + </ol> + + + + </td> + + </tr> + + + + +</table> + + + + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="http://android-developers.blogspot.com/2010/12/new-gingerbread-api-strictmode.html">Using StrictMode</a></strong> — Developer blog post discussing StrictMode and how to use it for performance monitoring in your app. </li> +<li><strong><a href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a></strong> — Developer guide describing best practices for keeping your app responsive.</li> +<li><strong><a href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading for Performance</a></strong> — Developer blog post discussing ways to improve performance through multi-threading.</li> +</ul> +</td> +</tr> +</table> + + +<h2 id="listing">Google Play</h2> + +<p>To launch your app successfully on Google Play, raise its ratings, and make +sure that it is ready for promotional activities in the store, follow the +criteria below.</p> + +<table> + <tr> + <th style="width:2px;"> + Area + </th> + <th style="width:54px;"> + ID + </th> + <th> + Description + </th> + <th style="width:54px;"> + Tests + </th> + </tr> +<tr> +<td rowspan="2">Policies</td> + <td> + GP-P1 + </td> + <td> + App strictly adheres to the terms of the <a href="http://play.google.com/about/developer-content-policy.html">Google Play Developer Content Policy</a> and does not offer inappropriate content, does not use intellectual property or brand of others, and so on. + </td> + <td> + <a href="#gp">GP-all</a> + </td> + </tr> + + + <tr> + <td> + GP-P2 + </td> + <td> + <p style="margin-bottom:.5em;">App maturity level is set appropriately, based on the + <a href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=188189">Content Rating Guidelines</a>.</p> + + <p style="margin-bottom:.25em;">Especially, note that apps that request permission to use the device location cannot be given the maturity level "Everyone". </p> + </td> + <td> + <a href="#gp">GP-1</a> + </td> + </tr> + + + <tr> + + <td rowspan="3">App Details Page</td> + <td> + GP-D1 + </td> + <td> + <p style="margin-bottom:.5em;">App feature graphic follows the guidelines outlined in this + <a href="http://android-developers.blogspot.com/2011/10/android-market-featured-image.html">blog post</a>. Make sure that:</p> + + <ol style="margin-bottom:.5em;list-style-type:lower-alpha"> + <li>The app listing includes a high-quality feature graphic.</li> + <li>The feature graphic does not contain device images, screenshots, or small text that will be illegible when scaled down and displayed on the smallest screen size that your app is targeting.</li> + <li>The feature graphic does not resemble an advertisement.</li> + </ol> + + + </td> + + <td> + <a href="#gp">GP-1, GP-2</a> + </td> + + + </tr> + <tr> + <td> + GP-D2 + </td> + <td> + App screenshots and videos do not show or reference non-Android devices. + </td> + <td rowspan="2"><a href="#gp">GP-1</a></td> + </tr> + + <tr> + <td> + GP-D3 + </td> + <td> + App screenshots or videos do not + represent the content and experience of your app in a misleading way. + </td> + </tr> + <tr> + <td>User Support</td> + <td> + GP-X1 + </td> + <td>Common user-reported bugs in the Reviews tab of the Google Play page are addressed if they are + reproducible and occur on many different devices. If a bug occurs on only a few devices, + you should still address it if those devices are particularly popular or new. + </td> + + <td> + <a href="#gp">GP-1</a> + </td> + + </tr> +</table> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="http://play.google.com/about/developer-content-policy.html">Google Play Developer Program Policies</a></strong> — Guidelines for what is acceptable conent in Google Play. Please read and understand the and understand the policies before publishing. +<li><strong><a href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=188189">Rating your application content for Google Play</a></strong> — Help Center document describing content ratings levels and how to choose the appropriate one for your app.</li> +<li><strong><a href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=1078870">Graphic Assets for your Application +</a></strong> — Details about the graphic assets you need to upload before publishing.</li> +<li><strong><a href="http://android-developers.blogspot.com/2011/10/android-market-featured-image.html">Google Play Featured Image Guidelines +</a></strong> — Blog post discussing how to create an attractive, effective Featured Image for your app.</li> +<li><strong><a href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=113477&topic=2364761&ctx=topic">Supporting your users +</a></strong> — Help Center document describing options for supporting users.</li> +</ul> +</td></tr> +</table> + + +<h2 id="test-environment">Setting Up a Test Environment</h2> + +<p>To assess the quality of your app, you need to set up a suitable +hardware or emulator environment for testing. </p> + +<p>The ideal test environment would +include a small number of actual hardware devices that represent key form +factors and hardware/software combinations currently available to consumers. +It's not necessary to test on <em>every</em> device that's on the market — +rather, you should focus on a small number of representative devices, even using +one or two devices per form factor. </p> + +<p>If you are not able to obtain actual hardware devices for testing, you should +set up emulated devices (AVDs) to represent the most common form factors and +hardware/software combinations. </p> + +<p>To go beyond basic testing, you can add more devices, more form factors, or +new hardware/software combinations to your test environment. You can also +increase the number or complexity of tests and quality criteria. </p> + + +<h2 id="tests"> + Test Procedures +</h2> + +<p>These test procedures help you discover various types of quality issues in your app. You can combine the tests or integrate groups of tests together in your own test plans. See the sections above for references that associate specific criteria with specific tests. </p> + +<table> + <tr> + <th style="width:2px;"> + Type + </th> + <th style="width:54px;"> + Test + </th> + <th> + Description + </th> + </tr> + <tr> + <td rowspan="11" id="core">Core Suite</td> + <td> + CR-0 + </td> + <td><p style="margin-bottom:.5em;">Navigate to all parts of the app — all screens, dialogs, settings, and all user flows. </p> + + <ol style="margin-bottom:.5em;list-style-type:lower-alpha"> + <li>If the application allows for editing or content creation, game play, or media playback, make sure to enter those flows to create or modify content.</li> + <li>While exercising the app, introduce transient changes in network connectivity, battery function, GPS or location availability, system load, and so on. </li> + </ol> + </td> + </tr> + <tr id="tg2"> + <td id="core"> + CR-1 + </td> + <td>From each app screen, press the device's Home key, then re-launch the app from the All Apps screen. + </td> + </tr> + <tr id="CR-2"> + <td> + CR-2 + </td> + <td>From each app screen, switch to another running app and then return to the app under test using the Recents app switcher. + </td> + </tr> + + <tr id="CR-3"> + <td> + CR-3 + </td> + <td>From each app screen (and dialogs), press the Back button. + </td> + </tr> + <tr id="CR-5"> + <td> + CR-5 + </td> + <td>From each app screen, rotate the device between landscape and portrait orientation at least three times. + </td> + </tr> + <tr id="CR-6"> + <td> + CR-6 + </td> + <td>Switch to another app to send the test app into the background. Go to Settings and check whether the test app has any services running while in the background. In Android 4.0 and higher, go to the Apps screen and find the app in the "Running" tab. In earlier versions, use "Manage Applications" to check for running services. + </td> + </tr> + + + <tr id="CR-7"> + <td> + CR-7 + </td> + <td> + Press the power button to put the device to sleep, then press the power button again to + awaken the screen. + </td> + </tr> + <tr id="CR-8"> + <td> + CR-8 + </td> + <td> + Set the device to lock when the power button is pressed. Press the power button to put the device to sleep, then press the power button again to + awaken the screen, then unlock the device. + </td> + </tr> + <tr id="CR-9"> + <!-- Hardware features --> + <td> + CR-9 + </td> + <td> + For devices that have slide-out keyboards, slide the keyboard in and out at least once. For devices that have keyboard docks, attach the device to the keyboard dock. + </td> + </tr> + <tr id="CR-10"> + <td> + CR-10 + </td> + <td> + For devices that have an external display port, plug-in the external display. + </td> + </tr> + <tr id="CR-11"> + <td> + CR-11 + </td> + <td>Examine the permissions requested by the app by going to Settings > App Info. + </td> + </tr> + <tr id="tg3"> + <td>Install on SD Card</td> + <td> + SD-1 + </td> + <td> + <p style="margin-bottom:.5em;">Repeat <em>Core Suite</em> with app installed to <a href="{@docRoot}guide/topics/data/install-location.html">device SD card</a> (if supported by app).</p> + + <p style="margin-bottom:.25em;">To move the app to SD card, you can use Settings > App Info > Move to SD Card.</p> + </td> + </tr> + + + + <tr id="tg3"> + <td>Hardware acceleration</td> + <td> + HA-1 + </td> + <td> + <p style="margin-bottom:.5em;">Repeat <em>Core Suite</em> with hardware acceleration enabled.</p> + + <p style="margin-bottom:.25em;">To force-enable hardware acceleration (where supported by device), add <code>hardware-accelerated="true"</code> to the <code><application></code> in the app manifest and recompile.</p> + </td> + </tr> + + <tr id="tg3"> + <td>Performance Monitoring</td> + <td> + PM-1 + </td> + <td> + <p style="margin-bottom:.5em;">Repeat <em>Core Suite</em> with StrictMode profiling enabled <a href="#strictmode">as described below</a>. <p style="margin-bottom:.25em;">Pay close attention to garbage collection and its impact on the user experience.</p> + </td> + </tr> + + <tr id="tg4"> + <td rowspan="3">Google Play</td> + <td> + GP-1 + </td> + <td> + Sign into the <a href="https://play.google.com/apps/publish/">Developer Console</a> to review your developer profile, app description, screenshots, feature graphic, maturity settings, and user feedback. + </td> + </tr> + <tr id="tg4"> + <td> + GP-2 + </td> + <td> + Download your feature graphic and screenshots and scale them down to match the display sizes on the devices and form factors you are targeting. + </td> + </tr> + <tr id="tg4"> + <td> + GP-4 + </td> + <td> + Review all graphical assets, media, text, code libraries, and other content packaged in the app or expansion file download. + </td> + </tr> + <tr id="tg4"> + <td>Payments</td> + <td> + GP-5 + </td> + <td> + Navigate to all screens of your app and enter all in-app purchase flows. + </td> +</tr> + +</table> + +<h3 id="strictmode"> +Testing with StrictMode +</h3> + +<p>For performance testing, we recommend enabling <code><a href="{@docRoot}reference/android/os/StrictMode.html">StrictMode</a></code> in your app and using it to catch operations on the main thread and other threads that could affect performance, network accesses, file reads/writes, and so on. + +You can set up a monitoring policy per thread using the <code><a href="{@docRoot}reference/android/os/StrictMode.ThreadPolicy.Builder.html">ThreadPolicy builder</a></code> and enable all supported monitoring in the <code>ThreadPolicy</code> using <code><a href="{@docRoot}reference/android/os/StrictMode.ThreadPolicy.Builder.html#detectAll()">detectAll()</a></code>.</p> + +<p>Make sure to enable <strong>visual notification</strong> of policy violations for the <code>ThreadPolicy</code> using <code><a href="{@docRoot}reference/android/os/StrictMode.ThreadPolicy.Builder.html#penaltyFlashScreen()">penaltyFlashScreen()</a></code>.</p> + diff --git a/docs/html/distribute/googleplay/quality/index.jd b/docs/html/distribute/googleplay/quality/index.jd new file mode 100644 index 000000000000..ffbcbabf09f9 --- /dev/null +++ b/docs/html/distribute/googleplay/quality/index.jd @@ -0,0 +1,46 @@ +page.title=App Quality +@jd:body + +<p><em>App quality</em> directly influences the long-term success of your app +— in terms of installs, user rating and reviews, engagement, and user +retention. Android users expect high quality from apps, even more so if they +puchased the apps or purchased their in-app products. At the same time, they +enjoy and value apps that put a priority on high quality and continuous +improvement.</p> + +<p>Before you publish an app on Google Play, it's important to make sure that +the app meets the basic quality expectations of users, across all of the form +factors and device types that the app is targeting. The documents in this +section help you assess your app's fundamental quality and they provide +resources to help you address issues that you find. </p> + +<div class="vspace size-1"> + +</div> +<div class="layout-content-row"> + <div class="layout-content-col span-4"> + <h4> + Core App Quality + </h4> + <p> + A set of core quality criteria that all Android apps should meet on all targeted devices. + </p><a href="{@docRoot}distribute/googleplay/quality/core.html">Learn more »</a> + </div> + <div class="layout-content-col span-4"> + <h4> + Tablet App Quality + </h4> + <p> + A set recommendations for delivering the best possible experience to tablet users. + </p><a href="{@docRoot}distribute/googleplay/quality/tablet.html">Learn more »</a> + </div> + <div class="layout-content-col span-4"> + <h4> + Improving App Quality + </h4> + <p> + Tips on continuously improving your app's quality, ratings, reviews, downloads, and engagement. + </p><a href="{@docRoot}distribute/googleplay/strategies/app-quality.html">Learn more + »</a> + </div> +</div> diff --git a/docs/html/distribute/googleplay/quality/tablet.jd b/docs/html/distribute/googleplay/quality/tablet.jd new file mode 100644 index 000000000000..b8a7a60acef5 --- /dev/null +++ b/docs/html/distribute/googleplay/quality/tablet.jd @@ -0,0 +1,555 @@ +page.title=Tablet App Quality Checklist +@jd:body + +<div id="qv-wrapper"><div id="qv"> +<h2>Checklist</h2> +<ol> + +<li><a href="#core-app-quality">1. Test for Core App Quality</a></li> +<li><a href="#optimize-layouts">2. Optimize your layouts</a></li> +<li><a href="#use-extra-space">3. Use the extra screen area</a></li> +<li><a href="#use-tablet-icons">4. Use assets designed for tablets</a></li> +<li><a href="#adjust-font-sizes">5. Adjust fonts and touch targets</a></li> +<li><a href="#adjust-widgets">6. Adjust homescreen widgets</a></li> +<li><a href="#offer-full-feature-set">7. Offer the app's full feature set</a></li> +<li><a href="#hardware-requirements">8. Don’t require hardware features</a></li> +<li><a href="#support-screens">9. Declare tablet screen support</a></li> +<li><a href="#google-play">10. Follow best practices for publishing in Google Play</a></li> + +</ol> +<h2>Testing</h2> +<ol> +<li><a href="#test-environment">Setting Up a Test Environment</a></li> +</ol> +</div></div> + + +<p>Before you publish an app on Google Play, it's important to make sure that +the app meets the basic expectations of tablet users, through compelling features +and an intuitive, well-designed UI. </p> + +<p>Tablets are a growing part of the Android installed base that offers new +opportunities for <a +href="{@docRoot}distribute/googleplay/spotlight/tablets.html">user engagement +and monetization</a>. If your app is targeting tablet users, this document helps +you focus on key aspects of quality, feature set, and UI that can have a +significant impact on the app's success. Each focus area is given as checklist +item, with each one comprising several smaller tasks or best practices.</p> + +<p>Although the checklist tasks below are numbered for convenience, +you can handle them in any order and address them to the extent that you feel +is right for your app. In the interest of delivering the best possible product +to your customers, follow the checklist recommendations +to the greatest extent possible. </p> + +<p>As you move through the checklist, you'll find links to support resources +that can help you address the topics raised in each task.</p> + + +<h2 id="core-app-quality">1. Test for Core App Quality</h2> + +<p>The first step in delivering a great tablet app experience is making sure +that it meets the <em>core app +quality criteria</em> for all of the devices and form factors that the app is +targeting. For complete information, see the <a +href="{@docRoot}distribute/googleplay/quality/core.html">Core App Quality Checklist</a>. +</p> + +<p>To assess the quality of your app on tablets — both for core app quality +and tablet app quality — you need to set up a suitable +hardware or emulator environment for testing. For more information, +see <a href="#test-environment">Setting Up a Test Environment</a>.</p> + + +<h2 id="optimize-layouts">2. Optimize your layouts for larger screens</h2> + +<p>Android makes it easy to develop an app that runs well on a wide range of +device screen sizes and form factors. This broad compatibility works in your +favor, since it helps you design a single app that you can distribute widely to +all of your targeted devices. However, to give your users the best possible +experience on each screen configuration — in particular on tablets +— you need to optimize your layouts and other UI components for each +targeted screen configuration. On tablets, optimizing your UI lets you take +full advantage of the additional screen available, such as to offer new features, +present new content, or enhance the experience in other ways to deepen user +engagement.</p> + +<p>If you developed your app for handsets and now want to distribute it to +tablets, you can start by making minor adjustments to your layouts, fonts, and +spacing. In some cases — such as for 7-inch tablets or for a game with +large canvas — these adjustments may be all +you need to make your app look great. In other cases, such as for larger +tablets, you can redesign parts of your UI to replace "stretched UI" with an +efficient multipane UI, easier navigation, and additional content. </p> + +<p>Here are some suggestions:</p> + +<div style="width:390px;float:right;margin:1.5em;margin-top:0em;"> +<img src="{@docRoot}images/training/app-navigation-multiple-sizes-multipane-bad.png" style="width:390px;padding:4px;margin-bottom:0em;"> +<p class="image-caption" style="padding:0em .5em .5em 2em"><span +style="font-weight:500;">Get rid of "stretched" UI</span>: On tablets, single-pane layouts lead to awkward whitespace and excessive line lengths. Use padding to reduce the width of UI elements and consider using multi-pane layouts.</p> +</div> + +<ul> +<li>Provide custom layouts as needed for <code>large</code> and +<code>xlarge</code> screens. You can also provide layouts that are loaded based +on the screen's <a href="{@docRoot}guide/practices/screens_support.html#NewQualifiers">shortest +dimension</a> or the <a href="{@docRoot}guide/practices/screens_support.html#NewQualifiers">minimum +available width and height</a>. </li> +<li>At a minimum, customize dimensions such as font sizes, margins, spacing for +larger screens, to improve use of space and content legibility. </li> +<li>Adjust positioning of UI controls so that they are easily accessible to +users when holding a tablet, such as toward the sides when in +landscape orientation.</li> +<li>Padding of UI elements should normally be larger on tablets than on handsets. A +<a href="{@docRoot}design/style/metrics-grids.html#48dp-rhythm">48dp rhythm</a> (and a 16dp +grid) is recommended.</li> +<li>Adequately pad text content so that it is not aligned directly along screen edges. +Use a minimum <code>16dp</code> padding around content near screen edges.</li> +</ul> + +<p>In particular, make sure that your layouts do not appear "stretched" +across the screen:</p> + +<ul> +<li>Lines of text should not be excessively long — optimize for a maximum +100 characters per line, with best results between 50 and 75.</li> +<li>ListViews and menus should not use the full screen width.</li> +<li>Use padding to manage the widths of onscreen elements or switch to a +multi-pane UI for tablets (see next section).</li> +</ul> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="http://developer.android.com/design/style/metrics-grids.html">Metrics and Grids +</a></strong> — Android Design document that explains ....</li> +<li><strong><a href="http://developer.android.com/design/style/devices-displays.html">Devices and Displays +</a></strong> — Android Design document that explains ....</li> +<li><strong><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></strong> — Developer documentation that explains the details of managing UI for best display on multiple screen sizes.</li> +<li><strong><a href="http://developer.android.com/guide/practices/screens_support.html#ConfigurationExamples">Configuration examples +</a></strong> — Examples of how to declare layouts and other resources for specific screen sizes.</a></li> +</ul> +</td> +</tr> +</table> + + +<h2 id="use-extra-space">3. Take advantage of extra screen area available on tablets</h2> + +<div style="width:290px;float:right;margin:1.5em;margin-bottom:0;margin-top:0;"> +<img src="{@docRoot}images/training/app-navigation-multiple-sizes-multipane-good.png" style="width:280px;padding:4px;margin-bottom:0em;"> +<p class="image-caption" style="padding:0em .5em .5em 1.5em"><span +style="font-weight:500;">Multi-pane layouts</span> result in a better visual balance on tablet screens, while offering more utility and legibility.</p> +</div> + +<p>Tablet screens provide significantly more screen real estate to your app, +especially when in landscape orientation. In particular, 10-inch tablets offer a +greatly expanded area, but even 7-inch tablets give you more space for +displaying content and engaging users. </p> + +<p>As you consider the UI of your app when running on tablets, make sure that it +is taking full advantage of extra screen area available on tablets. Here are +some suggestions:</p> + +<ul> +<li>Look for opportunities to include additional content or use an alternative +treatment of existing content.</li> +<li>Use <a href="{@docRoot}design/patterns/multi-pane-layouts.html">multi-pane +layouts</a> on tablet screens to combine single views into a compound view. This +lets you use the additional screen area more efficiently and makes it easier for +users to navigate your app. </li> +<li>Plan how you want the panels of your compound views to reorganize when +screen orientation changes.</li> + + + +<div style="width:490px;margin:1.5em auto 1.5em 0;"> + +<div style=""> +<img src="{@docRoot}images/ui-ex-single-panes.png" style="width:490px;padding:4px;margin-bottom:0em;" align="middle"> +<img src="{@docRoot}images/ui-ex-multi-pane.png" style="width:490px;padding:4px;margin-bottom:0em;"> +<p class="image-caption" style="padding:.5em"><span +style="font-weight:500;">Compound views</span> combine several single views from a handset UI <em>(above)</em> into a richer, more efficient UI for tablets <em>(below)</em>. </p> +</div> +</div> + +<li>While a single screen is implemented as an {@link android.app.Activity} +subclass, consider implementing individual content panels as {@link +android.app.Fragment} subclasses. This lets you maximize code reuse across +different form factors and across screens that share content.</li> +<li>Decide on which screen sizes you'll use a multi-pane UI, then provide the +different layouts in the appropriate screen size buckets (such as +<code>large</code>/<code>xlarge</code>) or minimum screen widths (such as +<code>sw600dp</code>/<code>sw720</code>).</li> +</ul> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="{@docRoot}design/patterns/multi-pane-layouts.html">Multi-pane Layouts</a></strong> — Android Design guide for using multi-pane UI, including examples of how to flatten navigation and integrate more content into your tablet UI.</li> +<li><strong><a href="{@docRoot}training/design-navigation/multiple-sizes.html">Planning for Multiple Touchscreen Sizes</a></strong> — Android Training class that walks you through the essentials of planning an intuitive, effective navigation for tablets and other devices. </li> +<li><strong><a href="{@docRoot}training/multiscreen/index.html">Designing for Multiple Screens</a></strong> — Android Training class that walks you through the essentials of planning an intuitive, effective navigation for tablets and other devices. </li> +</ul> +</td> +</tr> +</table> + + +<h2 id="use-tablet-icons">4. Use Icons and other assets that are designed for tablet screens</h2> + +<p>So that your app looks its best, make sure to use icons and other bitmap +assets that are created specifically for the densities used by tablet screens. +Specifically, you should create sets of alternative bitmap drawables for each +density in the range commonly supported by tablets.</p> + +<p class="table-caption">Raw asset sizes for icon types.<table> +<tr> +<th>Density </th> +<th colspa>Launcher</th> +<th>Action Bar</th> +<th>Small/Contextual</th> +<th>Notification</th> +</tr> +<tr> +<td><code>mdpi</code></td> +<td>48x48px</td> +<td>32x32px</td> +<td>16x16px</td> +<td>24x24px</td> +</tr> +<tr> +<td><code>hdpi</code></td> +<td>72x72px</td> +<td>48x48px</td> +<td>24x24px</td> +<td>36x36px</td> +</tr> +<tr> +<td><code>tvdpi</code></td> +<td><em>(use hdpi)</em></td> +<td><em>(use hdpi)</em></td> +<td><em>(use hdpi)</em></td> +<td><em>(use hdpi)</em></td> +</tr> +<tr> +<td><code>xhdpi</code></td> +<td>96x96px</td> +<td>64x64px</td> +<td>32x32px</td> +<td>48x48px</td> +</tr> + +</table> + +<p>Other points to consider: </p> + +<ul> +<li>Icons in the action bar, notifications, and launcher should be designed +according to the icon design guidelines and have the same physical size on +tablets as on phones.</li> +<li>Use density-specific <a +href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources"> +resource qualifiers</a> to ensure that the proper set of alternative resources +gets loaded.</li> +</ul> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="{@docRoot}design/style/iconography.html">Iconography</a></strong> — Android Design document that shows how to use various types of icons.</li> +<li><strong><a href="{@docRoot}guide/topics/resources/providing-resources.html">Providing Resources</a></strong> — Developer documentation on how to provide sets of layouts and drawable resources for specific ranges of device screens. </li> +<li><strong><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></strong> — API Guide documentation that explains the details of managing UI for best display on multiple screen sizes.</li> +<li><strong><a href="{@docRoot}training/basics/supporting-devices/screens.html">Supporting Different Screens</a></strong> — Android Training class that takes you through the process of optimizing the user experience for different screen sizes and densities.</li> +</ul> +</td> +</tr> +</table> + + +<h2 id="adjust-font-sizes">5. Adjust font sizes and touch targets for tablet screens</h2> + +<p>To make sure your app is easy to use on tablets, take some time to adjust the +font sizes and touch targets in your tablet UI, for all of the screen +configurations you are targeting. You can adjust font sizes through <a +href="{@docRoot}guide/topics/ui/themes.html">styleable attributes</a> or <a +href="{@docRoot}guide/topics/resources/more-resources.html#Dimension">dimension +resources</a>, and you can adjust touch targets through layouts and bitmap +drawables, as discussed above. </p> + +<p>Here are some considerations:</p> +<ul> +<li>Text should not be excessively large or small on tablet screen sizes and +densities. Make sure that labels are sized appropriately for the UI elements they +correspond to, and ensure that there are no improper line breaks in labels, +titles, and other elements.</li> +<li>The recommended touch-target size for onscreen elements is 48dp (32dp +minimum) — some adjustments may be needed in your tablet UI. Read <a +href="http://developer.android.com/design/style/metrics-grids.html">Metrics and +Grids +</a> to learn about implementation strategies to help most of your users. To +meet the accessibility needs of certain users, it may be appropriate to use +larger touch targets. </li> +<li>When possible, for smaller icons, expand the touchable area to more than +48dp using {@link android.view.TouchDelegate} or just centering the icon within +the transparent button.</li> +</ul> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="http://developer.android.com/design/style/metrics-grids.html">Metrics and Grids +</a></strong> — Android Design document that explains how to arrange and size touch targets and other UI elements on the screen.</li> +<li><strong><a href="{@docRoot}design/style/typography.html">Typography</a></strong> — Android Design document that gives an overview of how to use typography in your apps. </li> +<li><strong><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></strong> — Developer documentation that explains the details of managing UI for best display on multiple screen sizes.</li> +<li><strong><a href="{@docRoot}training/multiscreen/screendensities.html">Supporting Different Densities</a></strong> — Android Training class that shows you how to provide sets of layouts and drawable resources for specific ranges of device screens. </li> +</ul> +</td> +</tr> +</table> + + +<h2 id="adjust-widgets">6. Adjust sizes of home screen widgets for tablet screens</h2> + +<p>If your app includes a home screen widget, here are a few points to consider +to ensure a great user experience on tablet screens: </p> + +<ul> +<li>Make sure that the widget's default height and width are set appropriately +for tablet screens, as well as the minimum and maximum resize height and width. +</li> +<li>The widget should be resizable to 420dp or more, to span 5 or more home +screen rows (if this is a vertical or square widget) or columns (if this is a +horizontal or square widget). </li> +<li>Make sure that 9-patch images render correctly.</li> +<li>Use default system margins.</li> +<li>Set the app's <code>targetSdkVersion</code> to 14 or higher, if +possible.</li> +</ul> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="{@docRoot}guide/topics/appwidgets/index.html#MetaData">Adding the AppWidgetProviderInfo Metadata +</a></strong> — API Guide that explains how to set the height and width dimensions of a widget.</li> +<li><strong><a href="{@docRoot}guide/practices/ui_guidelines/widget_design.html">App Widget Design Guidelines</a></strong> — API Guide that provides best practices and techniques for designing and managing the size of widgets. </li> +</ul> +</td> +</tr> +</table> + + +<h2 id="offer-full-feature-set">7. Offer the app's full feature set to tablet users</h2> + +<p>Let your tablet users experience the best features of your app. Here are +some recommendations:</p> + +<ul> +<li>Design your app to offer at least the same set of features on tablets as it does on +handsets. </li> +<li>In exceptional cases, your app might omit or replace certain features on +tablets if they are not supported by the hardware or use-case of most tablets. +For example: +<ul> +<li>If the handset uses telephony features but telephony is not available on the +current tablet, you can omit or replace the related functionality.</li> +<li>Many tablets have a GPS sensor, but most users would not normally carry +their tablets while running. If your phone app provides functionality to let the +user record a GPS track of their runs while carrying their phones, the app would not need to +provide that functionality on tablets because the use-case is not +compelling.</li> +</ul> +</li> +<li>If you will omit a feature or capability from your tablet UI, make sure +that it is not accessible to users or that it offers “graceful degradation” +to a replacement feature (also see the section below on hardware features).</li> +</ul> + + +<h2 id="hardware-requirements">8. Don’t require hardware features that might not be available on tablets</h2> + +<p>Handsets and tablets typically offer slightly different hardware support for +sensors, camera, telephony, and other features. For example, many tablets are +available in a "Wi-Fi" configuration that does not include telephony support.</p> + +<p>To ensure that you can deliver a single APK broadly across the +your full customer base, make sure that your app does not have built-in +requirements for hardware features that aren't commonly available on tablets. +</p> + +<ul> +<li>Your app's manifest should not include <a +href="{@docRoot}guide/topics/manifest/uses-feature-element.html"><code><uses-feature></code></a> +elements for hardware features or capabilities that might not be +available on tablets, except when they are declared with the +<code>android:required=”false”</code> attribute. For example, your app should +not <em>require</em> features such as: +<ul> +<li><code>android.hardware.telephony</code></li> +<li><code>android.hardware.camera</code> (refers to back camera), or</li> +<li><code>android.hardware.camera.front</code></li> +</ul> +</li> +<li>Similarly, your app manifest should not include any <a +href="{@docRoot}guide/topics/manifest/permission-element.html"><code><permission></code></a> elements that <a +href="{@docRoot}guide/topics/manifest/uses-feature-element.html#permissions">imply +feature requirements</a> that might not be appropriate for tablets, except when +accompanied by a corresponding <code><uses-feature></code> element +declared with the <code>android:required=”false”</code> attribute.</li> +</ul> + +<p>In all cases, the app must function normally when the hardware features it +uses are not available and should offer “graceful degradation” and alternative +functionality where appropriate. For example, if GPS is not supported on the device, +your app could let the user set their location manually. The app should do +run-time checking for the hardware capability that it needs and handle as needed.</p> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html#permissions">Permissions that Imply Feature Requirements</a></strong> — A list of permissions that may cause unwanted filtering if declared in your app's manifest.</li> +<li><strong><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"><code><uses-feature></code></a></strong> — Description and reference documentation for the <code><uses-feature></code> manifest element.</li> +<li><strong><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html#testing">Testing the features required by your application</a></strong> — Description of how to determine the actual set of hardware and software requirements (explicit or implied) that your app requires.</li> +</ul> +</td> +</tr> +</table> + + +<h2 id="support-screens">9. Declare support for tablet screen configurations</h2> + +<p>To ensure that you can distribute your app to a broad range of tablets, +declare all the screen sizes that your app supports in its manifest:</p> + +<ul> +<li>Declare a <a +href="{@docRoot}guide/topics/manifest/supports-screens-element.html"><code><supports-screens></code></a> element +with appropriate attributes, as needed.</li> +<li>If the app declares a <code><compatible-screens></code> element in the +manifest, the element must include attributes that specify <em>all of the size and +density combinations for tablet screens</em> that the app supports. Note that, if possible, +you should avoid using this element in your app.</li> +</ul> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="{@docRoot}guide/topics/manifest/supports-screens-element.html"><code><supports-screens></code></a></strong> +— Description and reference documentation for the <code><supports-screens></code> +manifest element.</li> +<li><strong><a href="{@docRoot}guide/practices/screens_support.html#DeclaringScreenSizeSupport">Declaring Screen Size +Support</a></strong> — Developer documentation that explains the details of managing UI +for best display on multiple screen sizes.</li> +</ul> +</td> +</tr> +</table> + + +<h2 id="google-play">10. Follow best practices for publishing in Google Play</h2> + +<ul> +<li>Publish your app as a single APK for all screen sizes (handsets +and tablets), with a single Google Play listing: + <ul style="margin-top:.25em;"> + <li>Easier for users to find your app from search, browsing, or promotions</li> + <li>Easier for users to restore your app automatically if they get a new device.</li> + <li>Your ratings and download stats are consolidated across all devices.</li> + <li>Publishing a tablet app in a second listing can dilute ratings for your brand.</li> + </ul> +</li> +<li>If necessary, you can alternatively choose to deliver your app using <a +href="{@docRoot}guide/google/play/publishing/multiple-apks.html">Multiple APK Support</a>, +although in most cases using a single APK to reach all devices is strongly recommended.</li> + +<li>Highlight your app’s tablet capabilities in the product details page: + <ul style="margin-top:.25em;"> + <li>Add <strong>at least one screenshot taken while the app is running on a + tablet</strong>. It's recommended that you add one screenshot of landscape orientation + and one of portrait orientation, if possible. These screenshots make it clear to users + that your app is designed for tablets and highlight all the effort you've put into designing + a great tablet app experience.</li> + <li>Mention tablet support in the app description.</li> + <li>Include information about tablet support in the app's release notes and update + information.</li> + <li>In your app's promo video, add shots of your app running on a tablet.</li> + </ul> +</li> +<li>Make sure you are distributing to tablet devices. Check the app's Supported Devices +list in the <a href="https://play.google.com/apps/publish/">Developer Console</a> +to make sure your app is not filtered from tablet devices that you want to target.</li> + +<li>Let tablet users know about your app! Plan a marketing or advertising campaign that +highlights the use of your app on tablets.</li> +</ul> + +<table> +<tr> +<td><p>Related resources:</p> +<ul style="margin-top:-.5em;"> +<li><strong><a href="https://play.google.com/apps/publish/">Google Play Android Developer Console</a></strong> — The tools console for publishing your app to Android users.</li> +</ul> +</td> +</tr> +</table> + +<h2 id="test-environment">Setting Up a Test Environment for Tablets</h2> + +<p>To assess the quality of your app on tablets — both for core app quality +and tablet app quality — you need to set up a suitable +hardware or emulator environment for testing. </p> + +<p>The ideal test environment would +include a small number of actual hardware devices that represent key form +factors and hardware/software combinations currently available to consumers. +It's not necessary to test on <em>every</em> device that's on the market — +rather, you should focus on a small number of representative devices, even using +one or two devices per form factor. The table below provides an overview of +devices you could use for testing.</p> + +<p>If you are not able to obtain actual hardware devices for testing, you should +set up emulated devices (AVDs) to represent the most common form factors and +hardware/software combinations. See the table below for suggestions on the emulator +configurations to use. </p> + +<p>To go beyond basic testing, you can add more devices, more form factors, or +new hardware/software combinations to your test environment. For example, you +could include mid-size tablets, tablets with more or fewer hardware/software +features, and so on. You can also increase the number or complexity of tests +and quality criteria. </p> + +<p class="table-caption"><strong>Table 1</strong>. A typical tablet test environment might +include one or two devices from each row in the table below, with one of the +listed chipsets, platform versions, and hardware feature configurations.</p> + +<table> +<tr> +<th>Type</th> +<th>Size</th> +<th>Density</th> +<th>Version</th> +<th>AVD Skin</th> +</tr> + +<tr> +<td>7-inch tablet</td> +<td><span style="white-space:nowrap"><code>large</code> or</span><br /><code>-sw600</code></td> +<td><code>hdpi</code>,<br /><code>tvdpi</code></td> +<td>Android 4.0+</td> +<td>WXGA800-7in</td> +</tr> +<tr> +<td><span style="white-space:nowrap">10-inch</span> tablet</td> +<td><span style="white-space:nowrap"><code>xlarge</code> or</span><br /><code>-sw800</code></td> +<td><code>mdpi</code>,<br /><code>hdpi</code></td> +<td>Android 3.2+</td> +<td>WXGA800</td> +</tr> +</table>
\ No newline at end of file diff --git a/docs/html/distribute/googleplay/spotlight/index.jd b/docs/html/distribute/googleplay/spotlight/index.jd new file mode 100644 index 000000000000..6acd91433e64 --- /dev/null +++ b/docs/html/distribute/googleplay/spotlight/index.jd @@ -0,0 +1,46 @@ +page.title=Spotlight +walkthru=0 +header.hide=0 + +@jd:body + + +<p>Android developers, their apps, and their successes with Android and Google Play. </p> + + + +<div style="background: #F0F0F0; + border-top: 1px solid #DDD; + padding: 0px 0 24px 0; + overflow: auto; + clear:both; + margin-bottom:-10px; + margin-top:30px;""> + <div style="padding:0 0 0 29px;"> + <h4>Developer Story: Robot Invader</h4> + <img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px height:78px; + width: 78px; + float: left; + margin: 17px 20px 9px 0;" src= + "//g0.gstatic.com/android/market/com.robotinvader.knightmare/hi-256-0-9e08d83bc8d01649e167131d197ada1cd1783fb0"> + <div style="width:700px;"> + <p style="margin-top:26px;margin-bottom:12px;">Robot Invader chose + Android and Google Play as the launch platform for their first game,<br /> + <a data-g-event="Developers Page" data-g-label="Case Study Link" href= + "//play.google.com/store/apps/details?id=com.robotinvader.knightmare"><em>Wind-up + Knight</em></a>. + </p> + <p> + Hear from the developers how Android helped them reach millions of users + and more than 100 device models with a single app binary, then iterate rapidly to ensure + a great user experience. + </p> + </div> + <iframe style="float:left; + margin-right:24px; + margin-top:14px;" width="700" height="394" src= + "http://www.youtube.com/embed/hTtlLiUTowY" frameborder="0" allowfullscreen></iframe> + </div> +</div>
\ No newline at end of file diff --git a/docs/html/distribute/googleplay/spotlight/tablets.jd b/docs/html/distribute/googleplay/spotlight/tablets.jd new file mode 100644 index 000000000000..641b7940b64d --- /dev/null +++ b/docs/html/distribute/googleplay/spotlight/tablets.jd @@ -0,0 +1,283 @@ +page.title=Developer Stories: The Opportunity of Android Tablets +walkthru=0 +header.hide=0 + +@jd:body + + +<p>More and more, developers are investing in a full tablet experience +for their apps and are seeing those investments pay off big. The increased +screen area on tablets opens up a world of possibilities, allowing for more +engagement with the user — which can mean an increase in usage as well as +more monetization opportunities. And with the growing wave of Android tablets that +continue to hit the market, it’s an important piece of any developer’s mobile +offering. </p> + +<p>Here are some stories from developers who are seeing real results as they +expand their offering to include Android tablets.</p> + + +<div style="margin-bottom:2em;"><!-- START STORY --> + +<h3>Mint: More screen real estate = more engagement</h3> + + <img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px height:78px; + width: 78px; + float: left; + margin: 12px 20px 9px 20px;" src= + "https://lh5.ggpht.com/0xAIZJ1uE05b4RHNHgBBTIH6nRdPTY660T104xY7O2GbHXwab6YVmpU5yYg8yacfBg=w124"> + + <div style="list-style: none;height:100%; + float: right; + border-top: 1px solid #9C0; + width: 220px; + margin: 4px 20px;padding: .5em;"> + + + <h5>About the app</h5> + + + <ul> + <li><a href="http://play.google.com/store/apps/details?id=com.mint">Mint.com Personal Finance</a> by Intuit Inc.</li> + <li>Financial management app targeting 7- to 10-inch tablets</li> + </ul> + + <h5>Tablet Results</h5> + + <ul> + <li>Able to offer richer UI features</li> + <li>Much higher user engagement</li> + <li>Longer sessions — more Android tablet users have sessions longer than 5 minutes</li> + </ul> + + <div style="padding:.5em 0 0 1em;"> +<a href="http://play.google.com/store/apps/details?id=com.mint"> + <img alt="Android app on Google Play" + src="http://developer.android.com/images/brand/en_generic_rgb_wo_45.png" /> +</a> + + </div> + </div> + + <div style="line-height:1.4em;"> + <p style="margin-top:0;margin-bottom:12px;">When Intuit was thinking about +expanding their Mint mobile offering to include a version optimized for Android +tablets, they knew that taking the layout that worked for phones and simply +showing an enlarged version wouldn’t take full advantage of the opportunities +that tablets afford.</p> + + <p>“We knew we had a lot more real estate, and we wanted to provide a more +immersive experience for our users” said Ken Sun, Intuit Group Product Manager +at Mint.</p> + +<p>Intuit’s Mint app, which has a 4-star rating on Google Play, brings a number +of features to Android tablets that aren’t available for phones, including a +more visual presentation of personal financial data.</p> + +<p>“Whereas our app for phones is used throughout the day for quick sessions, +we’ve seen a larger percentage of our tablet usage happen in the evening, for +much longer sessions,” said Sun. “People are doing a lot more than just checking +their spending. They’re looking at historical trends, re-categorizing +transactions, analyzing the data and setting financial goals for the future +— digging much deeper and being more thoughtful. One example is how much +users are interacting with their own budgets in the tablet app. Customer budget +operations (view, edit, drill-down, etc.) are 7x higher on Android tablets than +they are on phones.”</p> + +<p>Fifty percent more Android tablet users have Mint sessions of 5 minutes or +longer than they do on phones. “We’ve found that phone usage is indicative of a +customer’s regular financial check-in, while tablet usage points towards more +analysis and interaction with that customer’s personal financial data. This is +the sort of immersive engagement experience we were looking for; the tablet and +phone apps serve as great complements to each other."</p> + </div> + + <div style="clear:both;margin-top:40px;width:auto;"> + + <a href=""><img src="{@docRoot}images/distribute/mint.png"></a> + + <div style="width:600px;margin-top:0px;padding:0 90px;"> + <p class="image-caption"><span style="font-weight:500;">Making the most of tablet screens</span>: Mint used the extra screen area on tablets to offer quick access to additional tools and information.</p> + </div> + + </div> + +</div> <!-- END STORY --> + + +<div style="margin:3em auto"><!-- START STORY --> + + +<h3>TinyCo: Monetization opportunities abound on tablets</h3> + + <img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px height:78px; + width: 78px; + float: left; + margin: 12px 20px 9px 20px;" src= + "https://lh5.ggpht.com/mO1TPos65MWJF_n8ZrXMbNCqIqsvN4dQV_rwNOU3pF6N_Ii3lSiCPe_H_MP8C1MK5UKo=w124"> + + + <div style="list-style: none;height:100%; + float: right; + border-top: 1px solid #9C0; + width: 220px; + margin: 4px 20px;padding: .5em;"> + + <h5>About the app</h5> + + <ul> + <li><a href="http://play.google.com/store/apps/details?id=com.tinyco.village">Tiny Village</a> by TinyCo</li> + <li>Game targeting 7- to 10-inch tablets and phones</li> + </ul> + + <h5>Tablet Results</h5> + + <ul> + <li>35% higher average revenue per paying user (ARPPU)</li> + <li>Consistent increase in user retention</li> + <li>3x increase in downloads to Android tablets in the last 6 months</li> + </ul> + + <div style="padding:.5em 0 0 1em;"> +<a href="http://play.google.com/store/apps/details?id=com.tinyco.village"> + <img alt="Android app on Google Play" + src="http://developer.android.com/images/brand/en_generic_rgb_wo_45.png" /> +</a> + + </div> + </div> + + <div style="line-height:1.4em;"> + <p style="margin-top:0;margin-bottom:12px;">Over a year ago, developer +TinyCo, makers of games such as Tiny Monsters, switched to a +simultaneous launch strategy for their products. They chose Android as one of their +primary launch platforms because of its large installed base and global reach. They +also knew that the growing base of Android tablet users represented a huge +opportunity. </p> + + <p>Tiny Village was their first title to take advantage of the strategy, and +it proved to be a winning one — especially in terms of Android +tablets.</p> + + <p> “With continued optimization of the gameplay experience and a genuine +commitment to our Android offering through our Griffin engine, all of our +metrics started to rise,” said Rajeev Nagpal, Head of Product at TinyCo. In +fact, they’ve seen Android tablet downloads more than triple in the last six +months.</p> + + <p>One of the first things they noticed about usage of Tiny Village on +tablets was an increase in average revenue per paying user (ARPPU)—about 35% +higher than on smaller-screen devices such as phones. Additionally, average +revenue per user ARPU is now about 35% higher as well. “The game is just much +more immersive on tablet.”</p> + + <p>In addition to an increase in monetization metrics, they’ve also seen a +consistent increase in retention over other platforms. “These are really +important metrics for games — if you can get users to both stay around +longer and spend more while they’re there, you have a recipe for success.”</p> + </div> + + <div style="clear:both;margin-top:40px;width:auto;"> + + <a href=""><img src="{@docRoot}images/distribute/tinyvillage.png"></a> + + <div style="width:600px;margin-top:0px;padding:0 90px;"> + <p class="image-caption"><span style="font-weight:500;">More monetization +on tablets</span>: On Android tablets TinyCo has seen higher ARPPU and user +retention than on phones.</p> + </div> + + </div> + +</div> <!-- END STORY --> + + +<div style="margin-bottom:2em;"><!-- START STORY --> + +<h3>Instapaper: Riding the growing wave of Android tablets</h3> + + + <img alt="" class="screenshot thumbnail" style="-webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px height:78px; + width: 78px; + float: left; + margin: 12px 20px 9px 20px;" src= + "https://lh3.ggpht.com/30KKcrIFO8V_wRfhnHaI9l0CLH_orIVFE7Xywtr9TBxAf0hi2BaZkKyBOs63Yfavpg=w124"> + + + <div style="list-style: none;height:100%; + float: right; + border-top: 1px solid #9C0; + width: 220px; + margin: 4px 20px;padding: .5em;"> + + + + + <h5>About the app</h5> + <ul> + <li><a href="http://play.google.com/store/apps/details?id=com.instapaper.android">Instapaper</a> by Mobelux</li> + <li>Content-browsing utility that targets 7- to 10-inch tablets and phones</li> + </ul> + + <h5>Tablet Results</h5> + + <ul> + <li>Tablets are now 50% of the app's installed base.</li> + </ul> + + <div style="padding:.5em 0 0 1em;"> +<a href="http://play.google.com/store/apps/details?id=com.instapaper.android"> + <img alt="Android app on Google Play" + src="http://developer.android.com/images/brand/en_generic_rgb_wo_45.png" /> +</a> + + </div> + </div> + + <div style="line-height:1.4em;"> + <p style="margin-top:0;margin-bottom:12px;">Instapaper for Android is an app +for saving web content to read later. Developer Mobelux decided that creating a +great UI for Android tablet users would be an essential part of their initial launch +plan.</p> + + <p>The app launched at the beginning of the summer of 2012, just in time to +take advantage of a new tide of Android tablets, including the <span +style="white-space:nowrap;">Nexus 7</span> tablet. The app has since seen huge +popularity among tablet users, in particular, on Nexus 7. On the day that +pre-orders of Nexus 7 began arriving, Mobelux saw a 600% jump in downloads of +its app on Google Play.</p> + + <p>“We saw a promising new set of Android tablets about to hit the market +and wanted to position ourselves to be ready for them” said Jeff Rock of +Mobelux. “It was a market that others were hesitant to explore, but the decision +to prioritize tablets has paid off very well for us.”</p> + + <p>Since that initial 600% jump in downloads, Instapaper for Android has +continued to see a successful run on Android tablets. In fact, Android tablets +now represent about 50% of their installed base. “With more and more Android +tablets coming online, we’re excited to see how our investment in Android +tablets continues to pay off.”</p> + </div> + + <div style="clear:both;margin-top:40px;width:auto;"> + + <a href=""><img src="{@docRoot}images/distribute/instapaper.png"></a> + + <div style="width:600px;margin-top:0px;padding:0 90px;"> + <p class="image-caption"><span style="font-weight:500;">Popular with +tablet users</span>: A great tablet UI and browsing convenience make Instapaper +popular with Android tablet users.</p> + </div> + + </div> + +</div> <!-- END STORY --> + + + diff --git a/docs/html/distribute/googleplay/strategies/app-quality.jd b/docs/html/distribute/googleplay/strategies/app-quality.jd index 6ea862be732e..ecc51dc9b5ac 100644 --- a/docs/html/distribute/googleplay/strategies/app-quality.jd +++ b/docs/html/distribute/googleplay/strategies/app-quality.jd @@ -1,10 +1,10 @@ -page.title=Improving App Quality +page.title=Improving App Quality After Launch @jd:body <div id="qv-wrapper"> <div id="qv"> -<h2>Strategies:</h2> -<ul> +<h2>Strategies</h2> +<ol> <li><a href="#listen">Listen to Your Users</a></li> <li><a href="#stability">Improve Stability and Eliminate Bugs</a></li> <li><a href="#responsiveness">Improve UI Responsiveness</a></li> @@ -13,7 +13,14 @@ page.title=Improving App Quality <li><a href="#features">Deliver the Right Set of Features</a></li> <li><a href="#integrate">Integrate with the System and Third-Party Apps</a></li> <li><a href="#details">Pay Attention to Details</a></li> -</ul> +</ol> + +<h2>You Should Also Read</h2> +<ol> +<li><a href="{@docRoot}distribute/googleplay/quality/core.html">Core App Quality Guidelines</a></li> +<li><a href="{@docRoot}distribute/googleplay/quality/tablet.html">Tablet App Quality Checklist</a></li> +</ol> + </div> </div> @@ -22,7 +29,7 @@ With thousands of new apps being published in Google Play every week, it's impor <p> A better app can go a very long way: a higher quality app will translate to higher user ratings, generally better rankings, more downloads, and higher retention (longer install periods). High-quality apps also have a much higher likelihood of getting some unanticipated positive publicity such as being featured in Google Play or getting social media buzz.</p> <p> -The upside to having a higher-quality app is obvious. However, it's not always clear how to make an app "better". The path to improving app quality isn't always well-lit. The term "quality" — along with "polish" and "fit and finish" — aren't always well-defined. Here we'll light the path by looking at some of the key factors in app quality and ways of improving your app along these dimensions.</p> +The upside to having a higher-quality app is obvious. However, it's not always clear how to make an app "better". This document looks at some of the key factors in app quality and ways of improving your app over time, after you've launched the app.</p> <h2 id="listen">Listen to Your Users</h2> <p> @@ -52,15 +59,14 @@ One sure-fire way to lose your users is to give them a slow, unresponsive UI. Re <p> You can improve your apps's UI responsiveness by moving long-running operations off the main thread to worker threads. Android offers built-in debugging facilities such as StrictMode for analyzing your app's performance and activities on the main thread. You can see more recommendations in <a href="http://www.youtube.com/watch?v=c4znvD-7VDA">Writing Zippy Android Apps</a>, a developer session from Google I/O 2010,</p> - <div class="sidebox-wrapper"> <div class="sidebox"> -<h2>More resources</h2> +<h3>More resources</h3> <ul> <li><a href="{@docRoot}design/index.html">Android Design</a></li> <li><a href="{@docRoot}guide/practices/performance.html">Designing for Performance</a></li> <li><a href="{@docRoot}guide/practices/responsiveness.html">Designing for Responsiveness</a> -<li><a href="{@docRoot}guide/practices/seamlessness.html">Designing for seamlessness</a> +<li><a href="{@docRoot}guide/practices/seamlessness.html">Designing for Seamlessness</a> </li> </ul> </div></div> @@ -73,18 +79,17 @@ Lastly, pointed out in the blog post <a href="http://android-developers.blogspot <h2 id="usability">Improve Usability</h2> <p> In usability and in app design too, you should listen carefully to your users. Ask a handful of real Android device users (friends, family, etc.) to try out your app and observe them as they interact with it. Look for cases where they get confused, are unsure of how to proceed, or are surprised by certain behaviors. Minimize these cases by rethinking some of the interactions in your app, perhaps working in some of the <a href="http://www.youtube.com/watch?v=M1ZBjlCRfz0">user interface patterns</a> the Android UI team discussed at Google I/O.</p> -<p> -In the same vein, two problems that can plague some Android user interfaces are small tap targets and excessively small font sizes. These are generally easy to fix and can make a big impact on usability and user satisfaction. As a general rule, optimize for ease of use and legibility, while minimizing, or at least carefully balancing, information density.</p> <div class="sidebox-wrapper"> <div class="sidebox"> -<h2>More resources</h2> -<ul> -As you are designing or evaluating your app's UI, make sure to read and become familiar with the <a href="{@docRoot}design/index.html">Android Design</a> guidelines. Included are many examples of UI patterns, styles, and building blocks, as well as tools for the design process.</li> -</ul> +<p> +As you are designing or evaluating your app's UI, make sure to read and become familiar with the <a href="/design/index.html">Android Design</a> guidelines. Included are many examples of UI patterns, styles, and building blocks, as well as tools for the design process.</p> </div></div> <p> +In the same vein, two problems that can plague some Android user interfaces are small tap targets and excessively small font sizes. These are generally easy to fix and can make a big impact on usability and user satisfaction. As a general rule, optimize for ease of use and legibility, while minimizing, or at least carefully balancing, information density.</p> + +<p> Another way to incrementally improve usability, based on real-world data, is to implement <a href="http://code.google.com/mobile/analytics/docs/">Analytics</a> throughout your app to log usage of particular sections. Consider demoting infrequently used sections to the overflow menu in the <a href="{@docRoot}design/patterns/actionbar.html">Action bar</a>, or removing them altogether. For often-used sections and UI elements, make sure they're immediately obvious and easily accessible in your app's UI so that users can get to them quickly.</p> <p> Lastly, usability is an extensive and well-documented subject, with close ties to interface design, cognitive science, and other disciplines.</p> diff --git a/docs/html/images/distribute/instapaper.png b/docs/html/images/distribute/instapaper.png Binary files differnew file mode 100644 index 000000000000..ffbf05295239 --- /dev/null +++ b/docs/html/images/distribute/instapaper.png diff --git a/docs/html/images/distribute/mint.png b/docs/html/images/distribute/mint.png Binary files differnew file mode 100644 index 000000000000..50d90d5bf82d --- /dev/null +++ b/docs/html/images/distribute/mint.png diff --git a/docs/html/images/distribute/tinyvillage.png b/docs/html/images/distribute/tinyvillage.png Binary files differnew file mode 100644 index 000000000000..cde77f09a66b --- /dev/null +++ b/docs/html/images/distribute/tinyvillage.png diff --git a/docs/html/images/ui-ex-multi-pane.png b/docs/html/images/ui-ex-multi-pane.png Binary files differnew file mode 100644 index 000000000000..188bb8bc116e --- /dev/null +++ b/docs/html/images/ui-ex-multi-pane.png diff --git a/docs/html/images/ui-ex-single-panes.png b/docs/html/images/ui-ex-single-panes.png Binary files differnew file mode 100644 index 000000000000..dff24a707b92 --- /dev/null +++ b/docs/html/images/ui-ex-single-panes.png diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 09528859c578..4ad13533bfc2 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -235,41 +235,31 @@ </li> <li class="nav-section"> - <div class="nav-section-header"><a href="<?cs var:toroot ?>training/multiscreen/index.html"> - <span class="en">Designing for Multiple Screens</span> - <span class="es">Cómo diseñar aplicaciones para varias pantallas</span> - <span class="ja">複数画面のデザイン</span> - <span class="ko">Designing for Multiple Screens</span> - <span class="ru">Designing for Multiple Screens</span> - <span class="zh-CN">针对多种屏幕进行设计</span> - </a></div> + <div class="nav-section-header"><a href="<?cs var:toroot ?>training/multiscreen/index.html" + zh-CN-lang="针对多种屏幕进行设计" + ja-lang="複数画面のデザイン" + es-lang="Cómo diseñar aplicaciones para varias pantallas" + >Designing for Multiple Screens</a> + </div> <ul> - <li><a href="<?cs var:toroot ?>training/multiscreen/screensizes.html"> - <span class="en">Supporting Different Screen Sizes</span> - <span class="es">Cómo admitir varios tamaños de pantalla</span> - <span class="ja">さまざまな画面サイズのサポート</span> - <span class="ko">다양한 화면 크기 지원</span> - <span class="ru">Supporting Different Screen Sizes</span> - <span class="zh-CN">支持各种屏幕尺寸</span> - </a> - </li> - <li><a href="<?cs var:toroot ?>training/multiscreen/screendensities.html"> - <span class="en">Supporting Different Screen Densities</span> - <span class="es">Cómo admitir varias densidades de pantalla</span> - <span class="ja">さまざまな画面密度のサポート</span> - <span class="ko">Supporting Different Screen Densities</span> - <span class="ru">Supporting Different Screen Densities</span> - <span class="zh-CN">支持各种屏幕密度</span> - </a> - </li> - <li><a href="<?cs var:toroot ?>training/multiscreen/adaptui.html"> - <span class="en">Implementing Adaptive UI Flows</span> - <span class="es">Cómo implementar interfaces de usuario adaptables</span> - <span class="ja">順応性のある UI フローの実装</span> - <span class="ko">Implementing Adaptive UI Flows</span> - <span class="ru">Implementing Adaptive UI Flows</span> - <span class="zh-CN">实施自适应用户界面流程</span> - </a> + <li><a href="<?cs var:toroot ?>training/multiscreen/screensizes.html" + zh-CN-lang="支持各种屏幕尺寸" + ko-lang="다양한 화면 크기 지원" + ja-lang="さまざまな画面サイズのサポート" + es-lang="Cómo admitir varios tamaños de pantalla" + >Designing for Multiple Screens</a> + </li> + <li><a href="<?cs var:toroot ?>training/multiscreen/screendensities.html" + zh-CN-lang="支持各种屏幕密度" + ja-lang="さまざまな画面密度のサポート" + es-lang="Cómo admitir varias densidades de pantalla" + >Supporting Different Screen Densities</a> + </li> + <li><a href="<?cs var:toroot ?>training/multiscreen/adaptui.html" + zh-CN-lang="实施自适应用户界面流程" + ja-lang="順応性のある UI フローの実装" + es-lang="Cómo implementar interfaces de usuario adaptables" + >Implementing Adaptive UI Flows</a> </li> </ul> </li> @@ -319,50 +309,36 @@ </li> <li class="nav-section"> - <div class="nav-section-header"><a href="<?cs var:toroot ?>training/monitoring-device-state/index.html"> - <span class="en">Optimizing Battery Life</span> - <span class="es">Cómo optimizar la duración de la batería</span> - <span class="ja">電池消費量の最適化</span> - <span class="ko">Optimizing Battery Life</span> - <span class="ru">Optimizing Battery Life</span> - <span class="zh-CN">优化电池使用时间</span> - </a></div> + <div class="nav-section-header"><a href="<?cs var:toroot ?>training/monitoring-device-state/index.html" + zh-CN-lang="优化电池使用时间" + ja-lang="電池消費量の最適化" + es-lang="Cómo optimizar la duración de la batería" + >Optimizing Battery Life</a> + </div> <ul> - <li><a href="<?cs var:toroot ?>training/monitoring-device-state/battery-monitoring.html"> - <span class="en">Monitoring the Battery Level and Charging State</span> - <span class="es">Cómo controlar el nivel de batería y el estado de carga</span> - <span class="ja">電池残量と充電状態の監視</span> - <span class="ko">Monitoring the Battery Level and Charging State</span> - <span class="ru">Monitoring the Battery Level and Charging State</span> - <span class="zh-CN">监控电池电量和充电状态</span> - </a> - </li> - <li><a href="<?cs var:toroot ?>training/monitoring-device-state/docking-monitoring.html"> - <span class="en">Determining and Monitoring the Docking State and Type</span> - <span class="es">Cómo determinar y controlar el tipo de conector y el estado de la conexión</span> - <span class="ja">ホルダーの装着状態とタイプの特定と監視</span> - <span class="ko">Determining and Monitoring the Docking State and Type</span> - <span class="ru">Determining and Monitoring the Docking State and Type</span> - <span class="zh-CN">确定和监控基座对接状态和类型</span> - </a> - </li> - <li><a href="<?cs var:toroot ?>training/monitoring-device-state/connectivity-monitoring.html"> - <span class="en">Determining and Monitoring the Connectivity Status</span> - <span class="es">Cómo determinar y controlar el estado de la conectividad</span> - <span class="ja">接続状態の特定と監視</span> - <span class="ko">Determining and Monitoring the Connectivity Status</span> - <span class="ru">Determining and Monitoring the Connectivity Status</span> - <span class="zh-CN">确定和监控网络连接状态</span> - </a> - </li> - <li><a href="<?cs var:toroot ?>training/monitoring-device-state/manifest-receivers.html"> - <span class="en">Manipulating Broadcast Receivers On Demand</span> - <span class="es">Cómo manipular los receptores de emisión bajo demanda</span> - <span class="ja">オンデマンドでのブロードキャスト レシーバ操作</span> - <span class="ko">Manipulating Broadcast Receivers On Demand</span> - <span class="ru">Manipulating Broadcast Receivers On Demand</span> - <span class="zh-CN">根据需要操作广播接收器</span> - </a> + <li><a href="<?cs var:toroot ?>training/monitoring-device-state/battery-monitoring.html" + zh-CN-lang="监控电池电量和充电状态" + ja-lang="電池残量と充電状態の監視" + es-lang="Cómo controlar el nivel de batería y el estado de carga" + >Monitoring the Battery Level and Charging State</a> + </li> + <li><a href="<?cs var:toroot ?>training/monitoring-device-state/docking-monitoring.html" + zh-CN-lang="确定和监控基座对接状态和类型" + ja-lang="ホルダーの装着状態とタイプの特定と監視" + es-lang="Cómo determinar y controlar el tipo de conector y el estado de la conexión" + >Determining and Monitoring the Docking State and Type</a> + </li> + <li><a href="<?cs var:toroot ?>training/monitoring-device-state/connectivity-monitoring.html" + zh-CN-lang="确定和监控网络连接状态" + ja-lang="接続状態の特定と監視" + es-lang="Cómo determinar y controlar el estado de la conectividad" + >Determining and Monitoring the Connectivity Status</a> + </li> + <li><a href="<?cs var:toroot ?>training/monitoring-device-state/manifest-receivers.html" + zh-CN-lang="根据需要操作广播接收器" + ja-lang="オンデマンドでのブロードキャスト レシーバ操作" + es-lang="Cómo manipular los receptores de emisión bajo demanda" + >Manipulating Broadcast Receivers On Demand</a> </li> </ul> </li> diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index fdb6818d3912..61418fba734b 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -1737,7 +1737,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { streamState.readSettings(); // unmute stream that was muted but is not affect by mute anymore - if (streamState.muteCount() != 0 && !isStreamAffectedByMute(streamType)) { + if (streamState.muteCount() != 0 && !isStreamAffectedByMute(streamType) && + !isStreamMutedByRingerMode(streamType)) { int size = streamState.mDeathHandlers.size(); for (int i = 0; i < size; i++) { streamState.mDeathHandlers.get(i).mMuteCount = 1; @@ -2591,6 +2592,18 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public synchronized void readSettings() { int remainingDevices = AudioSystem.DEVICE_OUT_ALL; + // do not read system stream volume from settings: this stream is always aliased + // to another stream type and its volume is never persisted. Values in settings can + // only be stale values + if ((mStreamType == AudioSystem.STREAM_SYSTEM) || + (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) { + mLastAudibleIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, + 10 * AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]); + mIndex.put(AudioSystem.DEVICE_OUT_DEFAULT, + 10 * AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]); + return; + } + for (int i = 0; remainingDevices != 0; i++) { int device = (1 << i); if ((device & remainingDevices) == 0) { @@ -2621,11 +2634,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { // a last audible index of 0 should never be stored for ring and notification // streams on phones (voice capable devices). - // same for system stream on phones and tablets - if ((lastAudibleIndex == 0) && - ((mVoiceCapable && - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) || - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_SYSTEM))) { + if ((lastAudibleIndex == 0) && mVoiceCapable && + (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) { lastAudibleIndex = AudioManager.DEFAULT_STREAM_VOLUME[mStreamType]; // Correct the data base sendMsg(mAudioHandler, @@ -2639,11 +2649,9 @@ public class AudioService extends IAudioService.Stub implements OnFinished { mLastAudibleIndex.put(device, getValidIndex(10 * lastAudibleIndex)); // the initial index should never be 0 for ring and notification streams on phones // (voice capable devices) if not in silent or vibrate mode. - // same for system stream on phones and tablets if ((index == 0) && (mRingerMode == AudioManager.RINGER_MODE_NORMAL) && - ((mVoiceCapable && - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) || - (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_SYSTEM))) { + mVoiceCapable && + (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_RING)) { index = lastAudibleIndex; // Correct the data base sendMsg(mAudioHandler, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 109654083987..ba7501b896cd 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -46,7 +46,6 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.Reader; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; @@ -64,25 +63,35 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final String KEY_SYSTEM = "system"; private static final String KEY_SECURE = "secure"; + private static final String KEY_GLOBAL = "global"; private static final String KEY_LOCALE = "locale"; - //Version 2 adds STATE_WIFI_CONFIG - private static final int STATE_VERSION_1 = 1; - private static final int STATE_VERSION_1_SIZE = 4; - // Versioning of the state file. Increment this version // number any time the set of state items is altered. - private static final int STATE_VERSION = 2; + private static final int STATE_VERSION = 3; + // Slots in the checksum array. Never insert new items in the middle + // of this array; new slots must be appended. private static final int STATE_SYSTEM = 0; private static final int STATE_SECURE = 1; private static final int STATE_LOCALE = 2; private static final int STATE_WIFI_SUPPLICANT = 3; private static final int STATE_WIFI_CONFIG = 4; - private static final int STATE_SIZE = 5; // The number of state items + private static final int STATE_GLOBAL = 5; + + private static final int STATE_SIZE = 6; // The current number of state items + + // Number of entries in the checksum array at various version numbers + private static final int STATE_SIZES[] = { + 0, + 4, // version 1 + 5, // version 2 added STATE_WIFI_CONFIG + STATE_SIZE // version 3 added STATE_GLOBAL + }; // Versioning of the 'full backup' format - private static final int FULL_BACKUP_VERSION = 1; + private static final int FULL_BACKUP_VERSION = 2; + private static final int FULL_BACKUP_ADDED_GLOBAL = 2; // added the "global" entry private static final int INTEGER_BYTE_COUNT = Integer.SIZE / Byte.SIZE; @@ -257,6 +266,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { byte[] systemSettingsData = getSystemSettings(); byte[] secureSettingsData = getSecureSettings(); + byte[] globalSettingsData = getGlobalSettings(); byte[] locale = mSettingsHelper.getLocaleData(); byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT); byte[] wifiConfigData = getFileData(mWifiConfigFile); @@ -267,6 +277,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data); stateChecksums[STATE_SECURE] = writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data); + stateChecksums[STATE_GLOBAL] = + writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, secureSettingsData, data); stateChecksums[STATE_LOCALE] = writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data); stateChecksums[STATE_WIFI_SUPPLICANT] = @@ -283,14 +295,18 @@ public class SettingsBackupAgent extends BackupAgentHelper { public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { + HashSet<String> movedToGlobal = new HashSet<String>(); + Settings.System.getMovedKeys(movedToGlobal); + Settings.Secure.getMovedKeys(movedToGlobal); + while (data.readNextHeader()) { final String key = data.getKey(); final int size = data.getDataSize(); if (KEY_SYSTEM.equals(key)) { - restoreSettings(data, Settings.System.CONTENT_URI); + restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal); mSettingsHelper.applyAudioSettings(); } else if (KEY_SECURE.equals(key)) { - restoreSettings(data, Settings.Secure.CONTENT_URI); + restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal); } else if (KEY_WIFI_SUPPLICANT.equals(key)) { int retainedWifiState = enableWifi(false); restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, data); @@ -317,6 +333,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { public void onFullBackup(FullBackupDataOutput data) throws IOException { byte[] systemSettingsData = getSystemSettings(); byte[] secureSettingsData = getSecureSettings(); + byte[] globalSettingsData = getGlobalSettings(); byte[] locale = mSettingsHelper.getLocaleData(); byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT); byte[] wifiConfigData = getFileData(mWifiConfigFile); @@ -339,6 +356,9 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (DEBUG_BACKUP) Log.d(TAG, secureSettingsData.length + " bytes of secure settings data"); out.writeInt(secureSettingsData.length); out.write(secureSettingsData); + if (DEBUG_BACKUP) Log.d(TAG, globalSettingsData.length + " bytes of global settings data"); + out.writeInt(globalSettingsData.length); + out.write(globalSettingsData); if (DEBUG_BACKUP) Log.d(TAG, locale.length + " bytes of locale data"); out.writeInt(locale.length); out.write(locale); @@ -371,20 +391,35 @@ public class SettingsBackupAgent extends BackupAgentHelper { int version = in.readInt(); if (DEBUG_BACKUP) Log.d(TAG, "Flattened data version " + version); - if (version == FULL_BACKUP_VERSION) { + if (version <= FULL_BACKUP_VERSION) { + // Generate the moved-to-global lookup table + HashSet<String> movedToGlobal = new HashSet<String>(); + Settings.System.getMovedKeys(movedToGlobal); + Settings.Secure.getMovedKeys(movedToGlobal); + // system settings data first int nBytes = in.readInt(); if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of settings data"); byte[] buffer = new byte[nBytes]; in.readFully(buffer, 0, nBytes); - restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI); + restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal); // secure settings nBytes = in.readInt(); if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data"); if (nBytes > buffer.length) buffer = new byte[nBytes]; in.readFully(buffer, 0, nBytes); - restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI); + restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal); + + // Global only if sufficiently new + if (version >= FULL_BACKUP_ADDED_GLOBAL) { + nBytes = in.readInt(); + if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of global settings data"); + if (nBytes > buffer.length) buffer = new byte[nBytes]; + in.readFully(buffer, 0, nBytes); + movedToGlobal.clear(); // no redirection; this *is* the global namespace + restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal); + } // locale nBytes = in.readInt(); @@ -430,14 +465,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { try { int stateVersion = dataInput.readInt(); - if (stateVersion == STATE_VERSION_1) { - for (int i = 0; i < STATE_VERSION_1_SIZE; i++) { - stateChecksums[i] = dataInput.readLong(); - } - } else if (stateVersion == STATE_VERSION) { - for (int i = 0; i < STATE_SIZE; i++) { - stateChecksums[i] = dataInput.readLong(); - } + for (int i = 0; i < STATE_SIZES[stateVersion]; i++) { + stateChecksums[i] = dataInput.readLong(); } } catch (EOFException eof) { // With the default 0 checksum we'll wind up forcing a backup of @@ -496,7 +525,18 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } - private void restoreSettings(BackupDataInput data, Uri contentUri) { + private byte[] getGlobalSettings() { + Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null, + null, null); + try { + return extractRelevantValues(cursor, Settings.Global.SETTINGS_TO_BACKUP); + } finally { + cursor.close(); + } + } + + private void restoreSettings(BackupDataInput data, Uri contentUri, + HashSet<String> movedToGlobal) { byte[] settings = new byte[data.getDataSize()]; try { data.readEntityData(settings, 0, settings.length); @@ -504,20 +544,23 @@ public class SettingsBackupAgent extends BackupAgentHelper { Log.e(TAG, "Couldn't read entity data"); return; } - restoreSettings(settings, settings.length, contentUri); + restoreSettings(settings, settings.length, contentUri, movedToGlobal); } - private void restoreSettings(byte[] settings, int bytes, Uri contentUri) { + private void restoreSettings(byte[] settings, int bytes, Uri contentUri, + HashSet<String> movedToGlobal) { if (DEBUG) { Log.i(TAG, "restoreSettings: " + contentUri); } - // Figure out the white list. + // Figure out the white list and redirects to the global table. String[] whitelist = null; if (contentUri.equals(Settings.Secure.CONTENT_URI)) { whitelist = Settings.Secure.SETTINGS_TO_BACKUP; } else if (contentUri.equals(Settings.System.CONTENT_URI)) { whitelist = Settings.System.SETTINGS_TO_BACKUP; + } else if (contentUri.equals(Settings.Global.CONTENT_URI)) { + whitelist = Settings.Global.SETTINGS_TO_BACKUP; } else { throw new IllegalArgumentException("Unknown URI: " + contentUri); } @@ -556,15 +599,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { continue; } + final Uri destination = (movedToGlobal.contains(key)) + ? Settings.Global.CONTENT_URI + : contentUri; + + // The helper doesn't care what namespace the keys are in if (settingsHelper.restoreValue(key, value)) { contentValues.clear(); contentValues.put(Settings.NameValueTable.NAME, key); contentValues.put(Settings.NameValueTable.VALUE, value); - getContentResolver().insert(contentUri, contentValues); + getContentResolver().insert(destination, contentValues); } if (DEBUG || true) { - Log.d(TAG, "Restored setting: " + key + "=" + value); + Log.d(TAG, "Restored setting: " + destination + " : "+ key + "=" + value); } } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index cc6656d0ad2b..ad35f7ff12ce 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -110,158 +110,12 @@ public class SettingsProvider extends ContentProvider { // table, shared across all users // These must match Settings.Secure.MOVED_TO_GLOBAL sSecureGlobalKeys = new HashSet<String>(); - sSecureGlobalKeys.add(Settings.Global.ADB_ENABLED); - sSecureGlobalKeys.add(Settings.Global.ASSISTED_GPS_ENABLED); - sSecureGlobalKeys.add(Settings.Global.BLUETOOTH_ON); - sSecureGlobalKeys.add(Settings.Global.CDMA_CELL_BROADCAST_SMS); - sSecureGlobalKeys.add(Settings.Global.CDMA_ROAMING_MODE); - sSecureGlobalKeys.add(Settings.Global.CDMA_SUBSCRIPTION_MODE); - sSecureGlobalKeys.add(Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE); - sSecureGlobalKeys.add(Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI); - sSecureGlobalKeys.add(Settings.Global.DATA_ROAMING); - sSecureGlobalKeys.add(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED); - sSecureGlobalKeys.add(Settings.Global.DEVICE_PROVISIONED); - sSecureGlobalKeys.add(Settings.Global.DISPLAY_DENSITY_FORCED); - sSecureGlobalKeys.add(Settings.Global.DISPLAY_SIZE_FORCED); - sSecureGlobalKeys.add(Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE); - sSecureGlobalKeys.add(Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE); - sSecureGlobalKeys.add(Settings.Global.INSTALL_NON_MARKET_APPS); - sSecureGlobalKeys.add(Settings.Global.MOBILE_DATA); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_DEV_BUCKET_DURATION); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_DEV_DELETE_AGE); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_DEV_PERSIST_BYTES); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_DEV_ROTATE_AGE); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_ENABLED); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_POLL_INTERVAL); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_REPORT_XT_OVER_DEV); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_SAMPLE_ENABLED); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_UID_BUCKET_DURATION); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_UID_DELETE_AGE); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_UID_PERSIST_BYTES); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_UID_ROTATE_AGE); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_UID_TAG_DELETE_AGE); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES); - sSecureGlobalKeys.add(Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE); - sSecureGlobalKeys.add(Settings.Global.NETWORK_PREFERENCE); - sSecureGlobalKeys.add(Settings.Global.NITZ_UPDATE_DIFF); - sSecureGlobalKeys.add(Settings.Global.NITZ_UPDATE_SPACING); - sSecureGlobalKeys.add(Settings.Global.NTP_SERVER); - sSecureGlobalKeys.add(Settings.Global.NTP_TIMEOUT); - sSecureGlobalKeys.add(Settings.Global.PDP_WATCHDOG_ERROR_POLL_COUNT); - sSecureGlobalKeys.add(Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS); - sSecureGlobalKeys.add(Settings.Global.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT); - sSecureGlobalKeys.add(Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS); - sSecureGlobalKeys.add(Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT); - sSecureGlobalKeys.add(Settings.Global.SAMPLING_PROFILER_MS); - sSecureGlobalKeys.add(Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL); - sSecureGlobalKeys.add(Settings.Global.SETUP_PREPAID_DETECTION_REDIR_HOST); - sSecureGlobalKeys.add(Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL); - sSecureGlobalKeys.add(Settings.Global.TETHER_DUN_APN); - sSecureGlobalKeys.add(Settings.Global.TETHER_DUN_REQUIRED); - sSecureGlobalKeys.add(Settings.Global.TETHER_SUPPORTED); - sSecureGlobalKeys.add(Settings.Global.THROTTLE_HELP_URI); - sSecureGlobalKeys.add(Settings.Global.THROTTLE_MAX_NTP_CACHE_AGE_SEC); - sSecureGlobalKeys.add(Settings.Global.THROTTLE_NOTIFICATION_TYPE); - sSecureGlobalKeys.add(Settings.Global.THROTTLE_POLLING_SEC); - sSecureGlobalKeys.add(Settings.Global.THROTTLE_RESET_DAY); - sSecureGlobalKeys.add(Settings.Global.THROTTLE_THRESHOLD_BYTES); - sSecureGlobalKeys.add(Settings.Global.THROTTLE_VALUE_KBITSPS); - sSecureGlobalKeys.add(Settings.Global.USB_MASS_STORAGE_ENABLED); - sSecureGlobalKeys.add(Settings.Global.USE_GOOGLE_MAIL); - sSecureGlobalKeys.add(Settings.Global.WEB_AUTOFILL_QUERY_URL); - sSecureGlobalKeys.add(Settings.Global.WIFI_COUNTRY_CODE); - sSecureGlobalKeys.add(Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS); - sSecureGlobalKeys.add(Settings.Global.WIFI_FREQUENCY_BAND); - sSecureGlobalKeys.add(Settings.Global.WIFI_IDLE_MS); - sSecureGlobalKeys.add(Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT); - sSecureGlobalKeys.add(Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS); - sSecureGlobalKeys.add(Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON); - sSecureGlobalKeys.add(Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY); - sSecureGlobalKeys.add(Settings.Global.WIFI_NUM_OPEN_NETWORKS_KEPT); - sSecureGlobalKeys.add(Settings.Global.WIFI_ON); - sSecureGlobalKeys.add(Settings.Global.WIFI_P2P_DEVICE_NAME); - sSecureGlobalKeys.add(Settings.Global.WIFI_SAVED_STATE); - sSecureGlobalKeys.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS); - sSecureGlobalKeys.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED); - sSecureGlobalKeys.add(Settings.Global.WIFI_WATCHDOG_ON); - sSecureGlobalKeys.add(Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED); - sSecureGlobalKeys.add(Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON); - sSecureGlobalKeys.add(Settings.Global.PACKAGE_VERIFIER_ENABLE); - sSecureGlobalKeys.add(Settings.Global.PACKAGE_VERIFIER_TIMEOUT); - sSecureGlobalKeys.add(Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE); - sSecureGlobalKeys.add(Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS); - sSecureGlobalKeys.add(Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS); - sSecureGlobalKeys.add(Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS); - sSecureGlobalKeys.add(Settings.Global.WTF_IS_FATAL); - sSecureGlobalKeys.add(Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD); - sSecureGlobalKeys.add(Settings.Global.BATTERY_DISCHARGE_THRESHOLD); - sSecureGlobalKeys.add(Settings.Global.SEND_ACTION_APP_ERROR); - sSecureGlobalKeys.add(Settings.Global.DROPBOX_AGE_SECONDS); - sSecureGlobalKeys.add(Settings.Global.DROPBOX_MAX_FILES); - sSecureGlobalKeys.add(Settings.Global.DROPBOX_QUOTA_KB); - sSecureGlobalKeys.add(Settings.Global.DROPBOX_QUOTA_PERCENT); - sSecureGlobalKeys.add(Settings.Global.DROPBOX_RESERVE_PERCENT); - sSecureGlobalKeys.add(Settings.Global.DROPBOX_TAG_PREFIX); - sSecureGlobalKeys.add(Settings.Global.ERROR_LOGCAT_PREFIX); - sSecureGlobalKeys.add(Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL); - sSecureGlobalKeys.add(Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD); - sSecureGlobalKeys.add(Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE); - sSecureGlobalKeys.add(Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES); - sSecureGlobalKeys.add(Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES); - sSecureGlobalKeys.add(Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS); - sSecureGlobalKeys.add(Settings.Global.CONNECTIVITY_CHANGE_DELAY); - sSecureGlobalKeys.add(Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED); - sSecureGlobalKeys.add(Settings.Global.CAPTIVE_PORTAL_SERVER); - sSecureGlobalKeys.add(Settings.Global.NSD_ON); - sSecureGlobalKeys.add(Settings.Global.SET_INSTALL_LOCATION); - sSecureGlobalKeys.add(Settings.Global.DEFAULT_INSTALL_LOCATION); - sSecureGlobalKeys.add(Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY); - sSecureGlobalKeys.add(Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY); - sSecureGlobalKeys.add(Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT); - sSecureGlobalKeys.add(Settings.Global.HTTP_PROXY); - sSecureGlobalKeys.add(Settings.Global.GLOBAL_HTTP_PROXY_HOST); - sSecureGlobalKeys.add(Settings.Global.GLOBAL_HTTP_PROXY_PORT); - sSecureGlobalKeys.add(Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST); - sSecureGlobalKeys.add(Settings.Global.SET_GLOBAL_HTTP_PROXY); - sSecureGlobalKeys.add(Settings.Global.DEFAULT_DNS_SERVER); - sSecureGlobalKeys.add(Settings.Global.PREFERRED_NETWORK_MODE); - sSecureGlobalKeys.add(Settings.Global.PREFERRED_CDMA_SUBSCRIPTION); + Settings.Secure.getMovedKeys(sSecureGlobalKeys); // Keys from the 'system' table now moved to 'global' // These must match Settings.System.MOVED_TO_GLOBAL sSystemGlobalKeys = new HashSet<String>(); - - sSystemGlobalKeys.add(Settings.Global.AIRPLANE_MODE_ON); - sSystemGlobalKeys.add(Settings.Global.AIRPLANE_MODE_RADIOS); - sSystemGlobalKeys.add(Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); - sSystemGlobalKeys.add(Settings.Global.AUTO_TIME); - sSystemGlobalKeys.add(Settings.Global.AUTO_TIME_ZONE); - sSystemGlobalKeys.add(Settings.Global.CAR_DOCK_SOUND); - sSystemGlobalKeys.add(Settings.Global.CAR_UNDOCK_SOUND); - sSystemGlobalKeys.add(Settings.Global.DESK_DOCK_SOUND); - sSystemGlobalKeys.add(Settings.Global.DESK_UNDOCK_SOUND); - sSystemGlobalKeys.add(Settings.Global.DOCK_SOUNDS_ENABLED); - sSystemGlobalKeys.add(Settings.Global.LOCK_SOUND); - sSystemGlobalKeys.add(Settings.Global.UNLOCK_SOUND); - sSystemGlobalKeys.add(Settings.Global.LOW_BATTERY_SOUND); - sSystemGlobalKeys.add(Settings.Global.POWER_SOUNDS_ENABLED); - sSystemGlobalKeys.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN); - sSystemGlobalKeys.add(Settings.Global.WIFI_SLEEP_POLICY); - sSystemGlobalKeys.add(Settings.Global.MODE_RINGER); - sSystemGlobalKeys.add(Settings.Global.WINDOW_ANIMATION_SCALE); - sSystemGlobalKeys.add(Settings.Global.TRANSITION_ANIMATION_SCALE); - sSystemGlobalKeys.add(Settings.Global.ANIMATOR_DURATION_SCALE); - sSystemGlobalKeys.add(Settings.Global.FANCY_IME_ANIMATIONS); - sSystemGlobalKeys.add(Settings.Global.COMPATIBILITY_MODE); - sSystemGlobalKeys.add(Settings.Global.EMERGENCY_TONE); - sSystemGlobalKeys.add(Settings.Global.CALL_AUTO_RETRY); - sSystemGlobalKeys.add(Settings.Global.DEBUG_APP); - sSystemGlobalKeys.add(Settings.Global.WAIT_FOR_DEBUGGER); - sSystemGlobalKeys.add(Settings.Global.SHOW_PROCESSES); - sSystemGlobalKeys.add(Settings.Global.ALWAYS_FINISH_ACTIVITIES); + Settings.System.getNonLegacyMovedKeys(sSystemGlobalKeys); } private boolean settingMovedToGlobal(final String name) { diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_not_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_not_connected.png Binary files differnew file mode 100644 index 000000000000..8fb71ba74901 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_not_connected.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_off.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_off.png Binary files differindex 7c6ca7534d81..ac7653594568 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_off.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_off.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_on.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_on.png Binary files differindex ff0ba07cde09..090d235d7911 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_on.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_bluetooth_on.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_not_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_not_connected.png Binary files differnew file mode 100644 index 000000000000..d0ce4f6fdddf --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_not_connected.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_off.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_off.png Binary files differindex 61eff946df08..2116449fb52c 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_off.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_off.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_on.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_on.png Binary files differindex b480a80bc638..1cc6e62f2029 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_on.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_bluetooth_on.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_not_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_not_connected.png Binary files differnew file mode 100644 index 000000000000..e312f8e9e7d1 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_not_connected.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_off.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_off.png Binary files differindex b4d9175a7a76..44cd31b347cd 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_off.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_off.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_on.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_on.png Binary files differindex 598d9676cb6b..62a518a9ecaf 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_on.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_bluetooth_on.png diff --git a/packages/SystemUI/res/layout/quick_settings.xml b/packages/SystemUI/res/layout/quick_settings.xml index c1bcdfe0a434..d119cf570702 100644 --- a/packages/SystemUI/res/layout/quick_settings.xml +++ b/packages/SystemUI/res/layout/quick_settings.xml @@ -22,10 +22,11 @@ android:background="@drawable/notification_panel_bg" > <!-- TODO: Put into ScrollView --> - <ScrollView + <com.android.systemui.statusbar.phone.QuickSettingsScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/close_handle_underlap" + android:overScrollMode="ifContentScrolls" > <com.android.systemui.statusbar.phone.QuickSettingsContainerView android:id="@+id/quick_settings_container" @@ -34,7 +35,7 @@ android:animateLayoutChanges="true" android:columnCount="@integer/quick_settings_num_columns" /> - </ScrollView> + </com.android.systemui.statusbar.phone.QuickSettingsScrollView> <View android:id="@+id/handle" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5023d234d43b..99036efa6b24 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -103,16 +103,16 @@ <!-- Initial velocity of the shade when collapsing on its own --> <dimen name="self_collapse_velocity">2000dp</dimen> <!-- Minimum final velocity of gestures interpreted as expand requests --> - <dimen name="fling_expand_min_velocity">200dp</dimen> + <dimen name="fling_expand_min_velocity">100dp</dimen> <!-- Minimum final velocity of gestures interpreted as collapse requests --> - <dimen name="fling_collapse_min_velocity">200dp</dimen> + <dimen name="fling_collapse_min_velocity">100dp</dimen> <!-- Cap on contribution of x dimension of gesture to overall velocity --> <dimen name="fling_gesture_max_x_velocity">200dp</dimen> <!-- Cap on overall resulting fling speed (s^-1) --> <dimen name="fling_gesture_max_output_velocity">3000dp</dimen> <!-- Minimum distance a fling must travel (anti-jitter) --> - <dimen name="fling_gesture_min_dist">10dp</dimen> + <dimen name="fling_gesture_min_dist">20dp</dimen> <!-- Minimum fraction of the display a gesture must travel, at any velocity, to qualify as a collapse request --> diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java index f93da08d7859..140cc8050b6e 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsActivity.java @@ -17,6 +17,7 @@ package com.android.systemui.recent; import android.app.Activity; +import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -30,6 +31,8 @@ import com.android.systemui.R; import com.android.systemui.SystemUIApplication; import com.android.systemui.statusbar.tablet.StatusBarPanel; +import java.util.List; + public class RecentsActivity extends Activity { public static final String TOGGLE_RECENTS_INTENT = "com.android.systemui.TOGGLE_RECENTS"; public static final String CLOSE_RECENTS_INTENT = "com.android.systemui.CLOSE_RECENTS"; @@ -122,11 +125,15 @@ public class RecentsActivity extends Activity { public void dismissAndGoBack() { if (mRecentsPanel != null) { - final SystemUIApplication app = (SystemUIApplication) getApplication(); - final RecentTasksLoader recentTasksLoader = app.getRecentTasksLoader(); - TaskDescription firstTask = mRecentsPanel.getBottomTask(); - if (firstTask != null && mRecentsPanel.simulateClick(firstTask)) { - // recents panel will take care of calling show(false); + final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + + final List<ActivityManager.RecentTaskInfo> recentTasks = + am.getRecentTasks(2, + ActivityManager.RECENT_WITH_EXCLUDED | + ActivityManager.RECENT_IGNORE_UNAVAILABLE); + if (recentTasks.size() > 1 && + mRecentsPanel.simulateClick(recentTasks.get(1).persistentId)) { + // recents panel will take care of calling show(false) through simulateClick return; } mRecentsPanel.show(false); diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java index 50b32f9c123a..6cb7decdbfd0 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java @@ -76,11 +76,11 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView } } - public View findViewForTask(TaskDescription task) { + public View findViewForTask(int persistentTaskId) { for (int i = 0; i < mLinearLayout.getChildCount(); i++) { View v = mLinearLayout.getChildAt(i); RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) v.getTag(); - if (holder.taskDescription == task) { + if (holder.taskDescription.persistentTaskId == persistentTaskId) { return v; } } diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index 3a890596c534..ff519960cac8 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -95,7 +95,7 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener public void setAdapter(TaskDescriptionAdapter adapter); public void setCallback(RecentsCallback callback); public void setMinSwipeAlpha(float minAlpha); - public View findViewForTask(TaskDescription task); + public View findViewForTask(int persistentTaskId); } private final class OnLongClickDelegate implements View.OnLongClickListener { @@ -518,24 +518,6 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener showIfReady(); } - public TaskDescription getBottomTask() { - if (mRecentsContainer != null) { - ViewGroup container = mRecentsContainer; - if (container instanceof RecentsScrollView) { - container = (ViewGroup) container.findViewById( - R.id.recents_linear_layout); - } - if (container.getChildCount() > 0) { - View v = container.getChildAt(container.getChildCount() - 1); - if (v.getTag() instanceof ViewHolder) { - ViewHolder h = (ViewHolder)v.getTag(); - return h.taskDescription; - } - } - } - return null; - } - public void onWindowAnimationStart() { if (mItemToAnimateInWhenWindowAnimationIsFinished != null) { final int startDelay = 100; @@ -590,7 +572,7 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener public void onTasksLoaded(ArrayList<TaskDescription> tasks, boolean firstScreenful) { mNumItemsWaitingForThumbnailsAndIcons = firstScreenful - ? tasks.size() : mRecentTaskDescriptions == null + ? tasks.size() : mRecentTaskDescriptions == null ? 0 : mRecentTaskDescriptions.size(); if (mRecentTaskDescriptions == null) { mRecentTaskDescriptions = new ArrayList<TaskDescription>(tasks); @@ -622,11 +604,11 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener setContentDescription(recentAppsAccessibilityDescription); } - public boolean simulateClick(TaskDescription task) { + public boolean simulateClick(int persistentTaskId) { if (mRecentsContainer instanceof RecentsScrollView){ RecentsScrollView scrollView = (RecentsScrollView) mRecentsContainer; - View v = scrollView.findViewForTask(task); + View v = scrollView.findViewForTask(persistentTaskId); if (v != null) { handleOnClick(v); return true; diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java index 5e0df4937320..47b01134f10a 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java @@ -77,11 +77,11 @@ public class RecentsVerticalScrollView extends ScrollView } } - public View findViewForTask(TaskDescription task) { + public View findViewForTask(int persistentTaskId) { for (int i = 0; i < mLinearLayout.getChildCount(); i++) { View v = mLinearLayout.getChildAt(i); RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) v.getTag(); - if (holder.taskDescription == task) { + if (holder.taskDescription.persistentTaskId == persistentTaskId) { return v; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index c9ec481b53ec..32b7c68466b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -24,6 +24,7 @@ import android.util.AttributeSet; import android.view.View; import com.android.systemui.R; +import com.android.systemui.statusbar.GestureRecorder; public class NotificationPanelView extends PanelView { @@ -47,9 +48,12 @@ public class NotificationPanelView extends PanelView { @Override public void fling(float vel, boolean always) { - ((PhoneStatusBarView) mBar).mBar.getGestureRecorder().tag( - "fling " + ((vel > 0) ? "open" : "closed"), - "notifications,v=" + vel); + GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); + if (gr != null) { + gr.tag( + "fling " + ((vel > 0) ? "open" : "closed"), + "notifications,v=" + vel); + } super.fling(vel, always); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index 6b9bc89ed37f..496219996b74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -21,14 +21,16 @@ public class PanelBar extends FrameLayout { public static final int STATE_OPENING = 1; public static final int STATE_OPEN = 2; - private PanelHolder mPanelHolder; - private ArrayList<PanelView> mPanels = new ArrayList<PanelView>(); - protected PanelView mTouchingPanel; + PanelHolder mPanelHolder; + ArrayList<PanelView> mPanels = new ArrayList<PanelView>(); + PanelView mTouchingPanel; private int mState = STATE_CLOSED; private boolean mTracking; + float mPanelExpandedFractionSum; + public void go(int state) { - LOG("go state: %d -> %d", mState, state); + if (DEBUG) LOG("go state: %d -> %d", mState, state); mState = state; } @@ -84,7 +86,7 @@ public class PanelBar extends FrameLayout { if (event.getAction() == MotionEvent.ACTION_DOWN) { final PanelView panel = selectPanelForTouchX(event.getX()); boolean enabled = panel.isEnabled(); - LOG("PanelBar.onTouch: state=%d ACTION_DOWN: panel %s %s", mState, panel, + if (DEBUG) LOG("PanelBar.onTouch: state=%d ACTION_DOWN: panel %s %s", mState, panel, (enabled ? "" : " (disabled)")); if (!enabled) return false; @@ -96,15 +98,21 @@ public class PanelBar extends FrameLayout { // called from PanelView when self-expanding, too public void startOpeningPanel(PanelView panel) { - LOG("startOpeningPanel: " + panel); + if (DEBUG) LOG("startOpeningPanel: " + panel); mTouchingPanel = panel; mPanelHolder.setSelectedPanel(mTouchingPanel); + for (PanelView pv : mPanels) { + if (pv != panel) { + pv.collapse(); + } + } } public void panelExpansionChanged(PanelView panel, float frac) { boolean fullyClosed = true; PanelView fullyOpenedPanel = null; - LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName()); + if (DEBUG) LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName()); + mPanelExpandedFractionSum = 0f; for (PanelView pv : mPanels) { final boolean visible = pv.getVisibility() == View.VISIBLE; // adjust any other panels that may be partially visible @@ -115,11 +123,10 @@ public class PanelBar extends FrameLayout { } fullyClosed = false; final float thisFrac = pv.getExpandedFraction(); - LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac); + mPanelExpandedFractionSum += (visible ? thisFrac : 0); + if (DEBUG) LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac); if (panel == pv) { if (thisFrac == 1f) fullyOpenedPanel = panel; - } else { - pv.setExpandedFraction(1f-frac); } } if (pv.getExpandedHeight() > 0f) { @@ -128,6 +135,7 @@ public class PanelBar extends FrameLayout { if (visible) pv.setVisibility(View.GONE); } } + mPanelExpandedFractionSum /= mPanels.size(); if (fullyOpenedPanel != null && !mTracking) { go(STATE_OPEN); onPanelFullyOpened(fullyOpenedPanel); @@ -136,7 +144,7 @@ public class PanelBar extends FrameLayout { onAllPanelsCollapsed(); } - LOG("panelExpansionChanged: end state=%d [%s%s ]", mState, + if (DEBUG) LOG("panelExpansionChanged: end state=%d [%s%s ]", mState, (fullyOpenedPanel!=null)?" fullyOpened":"", fullyClosed?" fullyClosed":""); } @@ -148,9 +156,10 @@ public class PanelBar extends FrameLayout { waiting = true; } else { pv.setExpandedFraction(0); // just in case + pv.setVisibility(View.GONE); } - pv.setVisibility(View.GONE); } + if (DEBUG) LOG("collapseAllPanels: animate=%s waiting=%s", animate, waiting); if (!waiting) { // it's possible that nothing animated, so we replicate the termination // conditions of panelExpansionChanged here @@ -160,20 +169,20 @@ public class PanelBar extends FrameLayout { } public void onPanelPeeked() { - LOG("onPanelPeeked"); + if (DEBUG) LOG("onPanelPeeked"); } public void onAllPanelsCollapsed() { - LOG("onAllPanelsCollapsed"); + if (DEBUG) LOG("onAllPanelsCollapsed"); } public void onPanelFullyOpened(PanelView openPanel) { - LOG("onPanelFullyOpened"); + if (DEBUG) LOG("onPanelFullyOpened"); } public void onTrackingStarted(PanelView panel) { mTracking = true; - if (panel != mTouchingPanel) { + if (DEBUG && panel != mTouchingPanel) { LOG("shouldn't happen: onTrackingStarted(%s) != mTouchingPanel(%s)", panel, mTouchingPanel); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java index abd82bdfb43f..241ac3e0d691 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java @@ -7,7 +7,7 @@ import android.widget.FrameLayout; public class PanelHolder extends FrameLayout { - private int mSelectedPanelIndex; + private int mSelectedPanelIndex = -1; private PanelBar mBar; public PanelHolder(Context context, AttributeSet attrs) { @@ -53,6 +53,7 @@ public class PanelHolder extends FrameLayout { public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: + PanelBar.LOG("PanelHolder got touch in open air, closing panels"); mBar.collapseAllPanels(true); break; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index d94dbe44abd5..ca1f75cf44f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -9,14 +9,9 @@ import android.util.Slog; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -import android.view.ViewGroup; import android.widget.FrameLayout; import com.android.systemui.R; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.BluetoothController; -import com.android.systemui.statusbar.policy.LocationController; -import com.android.systemui.statusbar.policy.NetworkController; public class PanelView extends FrameLayout { public static final boolean DEBUG = PanelBar.DEBUG; @@ -70,12 +65,16 @@ public class PanelView extends FrameLayout { } }; - private final Runnable mStopAnimator = new Runnable() { public void run() { - if (mTimeAnimator.isStarted()) { - mTimeAnimator.end(); - mRubberbanding = false; + private final Runnable mStopAnimator = new Runnable() { + @Override + public void run() { + if (mTimeAnimator.isStarted()) { + mTimeAnimator.end(); + mRubberbanding = false; + mClosing = false; + } } - }}; + }; private float mVel, mAccel; private int mFullHeight = 0; @@ -90,20 +89,22 @@ public class PanelView extends FrameLayout { mTimeAnimator.setTimeListener(mAnimationCallback); mTimeAnimator.start(); - - mRubberbanding = STRETCH_PAST_CONTENTS && mExpandedHeight > getFullHeight(); + + mRubberbanding = STRETCH_PAST_CONTENTS // is it enabled at all? + && mExpandedHeight > getFullHeight() // are we past the end? + && mVel >= -mFlingGestureMinDistPx; // was this not possibly a "close" gesture? if (mRubberbanding) { mClosing = true; } else if (mVel == 0) { - // if the panel is less than halfway open, close it + // if the panel is less than halfway open, close it mClosing = (mFinalTouchY / getFullHeight()) < 0.5f; } else { mClosing = mExpandedHeight > 0 && mVel < 0; } } else if (dtms > 0) { final float dt = dtms * 0.001f; // ms -> s - LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt); - LOG("tick: before: h=%d", (int) mExpandedHeight); + if (DEBUG) LOG("tick: v=%.2fpx/s dt=%.4fs", mVel, dt); + if (DEBUG) LOG("tick: before: h=%d", (int) mExpandedHeight); final float fh = getFullHeight(); boolean braking = false; @@ -136,12 +137,12 @@ public class PanelView extends FrameLayout { } float h = mExpandedHeight + mVel * dt; - + if (mRubberbanding && h < fh) { h = fh; } - LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false"); + if (DEBUG) LOG("tick: new h=%d closing=%s", (int) h, mClosing?"true":"false"); setExpandedHeightInternal(h); @@ -205,14 +206,14 @@ public class PanelView extends FrameLayout { loadDimens(); mHandleView = findViewById(R.id.handle); - LOG("handle view: " + mHandleView); + if (DEBUG) LOG("handle view: " + mHandleView); if (mHandleView != null) { mHandleView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { final float y = event.getY(); final float rawY = event.getRawY(); - LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f", + if (DEBUG) LOG("handle.onTouch: a=%s y=%.1f rawY=%.1f off=%.1f", MotionEvent.actionToString(event.getAction()), y, rawY, mTouchOffset); PanelView.this.getLocationOnScreen(mAbsPos); @@ -224,6 +225,7 @@ public class PanelView extends FrameLayout { mInitialTouchY = y; mVelocityTracker = VelocityTracker.obtain(); trackMovement(event); + mTimeAnimator.cancel(); // end any outstanding animations mBar.onTrackingStarted(PanelView.this); mTouchOffset = (rawY - mAbsPos[1]) - PanelView.this.getExpandedHeight(); break; @@ -263,9 +265,9 @@ public class PanelView extends FrameLayout { // if you've barely moved your finger, we treat the velocity as 0 // preventing spurious flings due to touch screen jitter - final float deltaY = (float)Math.abs(mFinalTouchY - mInitialTouchY); + final float deltaY = Math.abs(mFinalTouchY - mInitialTouchY); if (deltaY < mFlingGestureMinDistPx - || vel < mFlingGestureMinDistPx) { + || vel < mFlingExpandMinVelocityPx) { vel = 0; } @@ -273,7 +275,7 @@ public class PanelView extends FrameLayout { vel = -vel; } - LOG("gesture: dy=%f vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", + if (DEBUG) LOG("gesture: dy=%f vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", deltaY, mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity(), @@ -312,7 +314,7 @@ public class PanelView extends FrameLayout { @Override protected void onViewAdded(View child) { - LOG("onViewAdded: " + child); + if (DEBUG) LOG("onViewAdded: " + child); } public View getHandle() { @@ -324,7 +326,7 @@ public class PanelView extends FrameLayout { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - LOG("onMeasure(%d, %d) -> (%d, %d)", + if (DEBUG) LOG("onMeasure(%d, %d) -> (%d, %d)", widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight()); // Did one of our children change size? @@ -332,7 +334,7 @@ public class PanelView extends FrameLayout { if (newHeight != mFullHeight) { mFullHeight = newHeight; // If the user isn't actively poking us, let's rubberband to the content - if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted() + if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted() && mExpandedHeight > 0 && mExpandedHeight != mFullHeight) { mExpandedHeight = mFullHeight; } @@ -351,7 +353,7 @@ public class PanelView extends FrameLayout { @Override protected void onLayout (boolean changed, int left, int top, int right, int bottom) { - LOG("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, (int)mFullHeight); + if (DEBUG) LOG("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom, (int)mExpandedHeight, mFullHeight); super.onLayout(changed, left, top, right, bottom); } @@ -365,7 +367,7 @@ public class PanelView extends FrameLayout { if (!(STRETCH_PAST_CONTENTS && (mTracking || mRubberbanding)) && h > fh) h = fh; mExpandedHeight = h; - LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f"); + if (DEBUG) LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f"); requestLayout(); // FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); @@ -377,9 +379,9 @@ public class PanelView extends FrameLayout { private float getFullHeight() { if (mFullHeight <= 0) { - LOG("Forcing measure() since fullHeight=" + mFullHeight); - measure(MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); + if (DEBUG) LOG("Forcing measure() since fullHeight=" + mFullHeight); + measure(MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); } return mFullHeight; } @@ -397,11 +399,15 @@ public class PanelView extends FrameLayout { } public boolean isFullyExpanded() { - return mExpandedHeight == getFullHeight(); + return mExpandedHeight >= getFullHeight(); } public boolean isFullyCollapsed() { - return mExpandedHeight == 0; + return mExpandedHeight <= 0; + } + + public boolean isCollapsing() { + return mClosing; } public void setBar(PanelBar panelBar) { @@ -411,6 +417,8 @@ public class PanelView extends FrameLayout { public void collapse() { // TODO: abort animation or ongoing touch if (!isFullyCollapsed()) { + // collapse() should never be a rubberband, even if an animation is already running + mRubberbanding = false; fling(-mSelfCollapseVelocityPx, /*always=*/ true); } } @@ -418,10 +426,10 @@ public class PanelView extends FrameLayout { public void expand() { if (isFullyCollapsed()) { mBar.startOpeningPanel(this); - LOG("expand: calling fling(%s, true)", mSelfExpandVelocityPx); + if (DEBUG) LOG("expand: calling fling(%s, true)", mSelfExpandVelocityPx); fling (mSelfExpandVelocityPx, /*always=*/ true); } else if (DEBUG) { - LOG("skipping expansion: is expanded"); + if (DEBUG) LOG("skipping expansion: is expanded"); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 493a92a882f7..a12af8d69028 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -99,6 +99,7 @@ public class PhoneStatusBar extends BaseStatusBar { public static final boolean DEBUG = BaseStatusBar.DEBUG; public static final boolean SPEW = DEBUG; public static final boolean DUMPTRUCK = true; // extra dumpsys info + public static final boolean DEBUG_GESTURES = false; // additional instrumentation for testing purposes; intended to be left on during development public static final boolean CHATTY = DEBUG; @@ -247,7 +248,9 @@ public class PhoneStatusBar extends BaseStatusBar { DisplayMetrics mDisplayMetrics = new DisplayMetrics(); // XXX: gesture research - private GestureRecorder mGestureRec = new GestureRecorder("/sdcard/statusbar_gestures.dat"); + private final GestureRecorder mGestureRec = DEBUG_GESTURES + ? new GestureRecorder("/sdcard/statusbar_gestures.dat") + : null; private int mNavigationIconHints = 0; private final Animator.AnimatorListener mMakeIconsInvisible = new AnimatorListenerAdapter() { @@ -1350,7 +1353,9 @@ public class PhoneStatusBar extends BaseStatusBar { } } - mGestureRec.add(event); + if (DEBUG_GESTURES) { + mGestureRec.add(event); + } return false; } @@ -1630,8 +1635,10 @@ public class PhoneStatusBar extends BaseStatusBar { } } - pw.print(" status bar gestures: "); - mGestureRec.dump(fd, pw, args); + if (DEBUG_GESTURES) { + pw.print(" status bar gestures: "); + mGestureRec.dump(fd, pw, args); + } mNetworkController.dump(fd, pw, args); } @@ -1713,8 +1720,10 @@ public class PhoneStatusBar extends BaseStatusBar { // called by makeStatusbar and also by PhoneStatusBarView void updateDisplaySize() { mDisplay.getMetrics(mDisplayMetrics); - mGestureRec.tag("display", - String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); + if (DEBUG_GESTURES) { + mGestureRec.tag("display", + String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); + } } private View.OnClickListener mClearButtonListener = new View.OnClickListener() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 6517e7cfa634..15ac5c0a96a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -19,27 +19,14 @@ package com.android.systemui.statusbar.phone; import android.app.ActivityManager; import android.app.StatusBarManager; import android.content.Context; -import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Rect; -import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Log; import android.util.Slog; -import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; -import android.widget.FrameLayout; - import com.android.systemui.R; -import com.android.systemui.statusbar.BaseStatusBar; -import com.android.systemui.statusbar.policy.FixedSizeDrawable; public class PhoneStatusBarView extends PanelBar { private static final String TAG = "PhoneStatusBarView"; @@ -53,6 +40,7 @@ public class PhoneStatusBarView extends PanelBar { boolean mFullWidthNotifications; PanelView mFadingPanel = null; PanelView mNotificationPanel, mSettingsPanel; + private boolean mShouldFade; public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -112,7 +100,7 @@ public class PhoneStatusBarView extends PanelBar { if (DEBUG) { Slog.v(TAG, "notif frac=" + mNotificationPanel.getExpandedFraction()); } - return (mNotificationPanel.getExpandedFraction() == 1.0f) + return (mNotificationPanel.getExpandedFraction() > 0f) ? mSettingsPanel : mNotificationPanel; } @@ -120,7 +108,7 @@ public class PhoneStatusBarView extends PanelBar { // right 1/3 for quick settings. If you pull the status bar down a second time you'll // toggle panels no matter where you pull it down. - final float w = (float) getMeasuredWidth(); + final float w = getMeasuredWidth(); float region = (w * mSettingsPanelDragzoneFrac); if (DEBUG) { @@ -138,9 +126,18 @@ public class PhoneStatusBarView extends PanelBar { public void onPanelPeeked() { super.onPanelPeeked(); mBar.makeExpandedVisible(true); - if (mFadingPanel == null) { - mFadingPanel = mTouchingPanel; + } + + @Override + public void startOpeningPanel(PanelView panel) { + super.startOpeningPanel(panel); + // we only want to start fading if this is the "first" or "last" panel, + // which is kind of tricky to determine + mShouldFade = (mFadingPanel == null || mFadingPanel.isFullyExpanded()); + if (DEBUG) { + Slog.v(TAG, "start opening: " + panel + " shouldfade=" + mShouldFade); } + mFadingPanel = panel; } @Override @@ -153,6 +150,7 @@ public class PhoneStatusBarView extends PanelBar { @Override public void onPanelFullyOpened(PanelView openPanel) { mFadingPanel = openPanel; + mShouldFade = true; // now you own the fade, mister } @Override @@ -166,24 +164,24 @@ public class PhoneStatusBarView extends PanelBar { } @Override - public void panelExpansionChanged(PanelView pv, float frac) { - super.panelExpansionChanged(pv, frac); + public void panelExpansionChanged(PanelView panel, float frac) { + super.panelExpansionChanged(panel, frac); if (DEBUG) { Slog.v(TAG, "panelExpansionChanged: f=" + frac); } - if (mFadingPanel == pv - && mScrimColor != 0 && ActivityManager.isHighEndGfx()) { - // woo, special effects - final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2.2f)))); - // attenuate background color alpha by k - final int color = (int) ((float)(mScrimColor >>> 24) * k) << 24 | (mScrimColor & 0xFFFFFF); - mBar.mStatusBarWindow.setBackgroundColor(color); + if (panel == mFadingPanel && mScrimColor != 0 && ActivityManager.isHighEndGfx()) { + if (mShouldFade) { + frac = mPanelExpandedFractionSum; // don't judge me + // woo, special effects + final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2.2f)))); + // attenuate background color alpha by k + final int color = (int) ((mScrimColor >>> 24) * k) << 24 | (mScrimColor & 0xFFFFFF); + mBar.mStatusBarWindow.setBackgroundColor(color); + } } mBar.updateCarrierLabelVisibility(false); } - - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java index c31e138a621f..4ef35aace041 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java @@ -20,6 +20,7 @@ import android.app.ActivityManagerNative; import android.app.AlertDialog; import android.app.Dialog; import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -81,6 +82,7 @@ class QuickSettings { private DisplayManager mDisplayManager; private WifiDisplayStatus mWifiDisplayStatus; private PhoneStatusBar mStatusBarService; + private QuickSettingsModel.BluetoothState mBluetoothState; private BrightnessController mBrightnessController; private BluetoothController mBluetoothController; @@ -115,6 +117,7 @@ class QuickSettings { mContainerView = container; mModel = new QuickSettingsModel(context); mWifiDisplayStatus = new WifiDisplayStatus(); + mBluetoothState = new QuickSettingsModel.BluetoothState(); mHandler = new Handler(); Resources r = mContext.getResources(); @@ -128,6 +131,7 @@ class QuickSettings { IntentFilter filter = new IntentFilter(); filter.addAction(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED); + filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); mContext.registerReceiver(mReceiver, filter); } @@ -739,6 +743,10 @@ class QuickSettings { mModel.onWifiDisplayStateChanged(mWifiDisplayStatus); } + private void applyBluetoothStatus() { + mModel.onBluetoothStateChange(mBluetoothState); + } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -748,6 +756,12 @@ class QuickSettings { mWifiDisplayStatus = status; applyWifiDisplayStatus(); } + if (intent.getAction().equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { + int status = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, + BluetoothAdapter.STATE_DISCONNECTED); + mBluetoothState.connected = (status == BluetoothAdapter.STATE_CONNECTED); + applyBluetoothStatus(); + } } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java index 6b9a32108186..0e5361735b37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java @@ -77,6 +77,9 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, static class BrightnessState extends State { boolean autoBrightness; } + public static class BluetoothState extends State { + boolean connected = false; + } /** The callback to update a given tile. */ interface RefreshCallback { @@ -173,7 +176,7 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, private QuickSettingsTileView mBluetoothTile; private RefreshCallback mBluetoothCallback; - private State mBluetoothState = new State(); + private BluetoothState mBluetoothState = new BluetoothState(); private QuickSettingsTileView mBatteryTile; private RefreshCallback mBatteryCallback; @@ -354,7 +357,7 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mWifiState.label = removeDoubleQuotes(enabledDesc); } else if (wifiNotConnected) { mWifiState.iconId = R.drawable.ic_qs_wifi_0; - mWifiState.label = r.getString(R.string.quick_settings_wifi_not_connected); + mWifiState.label = r.getString(R.string.quick_settings_wifi_label); } else { mWifiState.iconId = R.drawable.ic_qs_wifi_no_network; mWifiState.label = r.getString(R.string.quick_settings_wifi_off_label); @@ -398,7 +401,10 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mBluetoothCallback = cb; final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - onBluetoothStateChange(adapter.isEnabled()); + mBluetoothState.enabled = adapter.isEnabled(); + mBluetoothState.connected = + (adapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED); + onBluetoothStateChange(mBluetoothState); } boolean deviceSupportsBluetooth() { return (BluetoothAdapter.getDefaultAdapter() != null); @@ -406,11 +412,20 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, // BluetoothController callback @Override public void onBluetoothStateChange(boolean on) { + mBluetoothState.enabled = on; + onBluetoothStateChange(mBluetoothState); + } + public void onBluetoothStateChange(BluetoothState bluetoothStateIn) { // TODO: If view is in awaiting state, disable Resources r = mContext.getResources(); - mBluetoothState.enabled = on; - if (on) { - mBluetoothState.iconId = R.drawable.ic_qs_bluetooth_on; + mBluetoothState.enabled = bluetoothStateIn.enabled; + mBluetoothState.connected = bluetoothStateIn.connected; + if (mBluetoothState.enabled) { + if (mBluetoothState.connected) { + mBluetoothState.iconId = R.drawable.ic_qs_bluetooth_on; + } else { + mBluetoothState.iconId = R.drawable.ic_qs_bluetooth_not_connected; + } mBluetoothState.label = r.getString(R.string.quick_settings_bluetooth_label); } else { mBluetoothState.iconId = R.drawable.ic_qs_bluetooth_off; @@ -632,5 +647,4 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, onNextAlarmChanged(); onBugreportChanged(); } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsScrollView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsScrollView.java new file mode 100644 index 000000000000..8a2f8d684769 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsScrollView.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ScrollView; + +public class QuickSettingsScrollView extends ScrollView { + + public QuickSettingsScrollView(Context context) { + super(context); + } + + public QuickSettingsScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public QuickSettingsScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + // Y U NO PROTECTED + private int getScrollRange() { + int scrollRange = 0; + if (getChildCount() > 0) { + View child = getChildAt(0); + scrollRange = Math.max(0, + child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop)); + } + return scrollRange; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + final int range = getScrollRange(); + if (range == 0) { + return false; + } + + return super.onTouchEvent(ev); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsPanelView.java index f9d9dac01047..e555277cc1ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsPanelView.java @@ -29,6 +29,7 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; +import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.LocationController; @@ -95,9 +96,12 @@ public class SettingsPanelView extends PanelView { @Override public void fling(float vel, boolean always) { - ((PhoneStatusBarView) mBar).mBar.getGestureRecorder().tag( - "fling " + ((vel > 0) ? "open" : "closed"), - "settings,v=" + vel); + GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); + if (gr != null) { + gr.tag( + "fling " + ((vel > 0) ? "open" : "closed"), + "settings,v=" + vel); + } super.fling(vel, always); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java index 5007cf4a3033..ff0663074ee3 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java @@ -26,6 +26,7 @@ import android.net.Uri; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; import android.os.Bundle; +import android.os.UserHandle; import android.util.Log; import com.android.internal.app.AlertActivity; @@ -90,7 +91,7 @@ public class UsbAccessoryUriActivity extends AlertActivity intent.addCategory(Intent.CATEGORY_BROWSABLE); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { - startActivity(intent); + startActivityAsUser(intent, UserHandle.CURRENT); } catch (ActivityNotFoundException e) { Log.e(TAG, "startActivity failed for " + mUri); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java index 030a2615b7d0..3eccccdd75bf 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java @@ -16,23 +16,21 @@ package com.android.systemui.usb; -import android.app.Activity; import android.app.AlertDialog; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.hardware.usb.IUsbManager; -import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -42,7 +40,6 @@ import android.widget.TextView; import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; - import com.android.systemui.R; public class UsbConfirmActivity extends AlertActivity @@ -62,10 +59,10 @@ public class UsbConfirmActivity extends AlertActivity public void onCreate(Bundle icicle) { super.onCreate(icicle); - Intent intent = getIntent(); - mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + Intent intent = getIntent(); + mDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); - mResolveInfo = (ResolveInfo)intent.getParcelableExtra("rinfo"); + mResolveInfo = (ResolveInfo) intent.getParcelableExtra("rinfo"); PackageManager packageManager = getPackageManager(); String appName = mResolveInfo.loadLabel(packageManager).toString(); @@ -117,7 +114,8 @@ public class UsbConfirmActivity extends AlertActivity try { IBinder b = ServiceManager.getService(USB_SERVICE); IUsbManager service = IUsbManager.Stub.asInterface(b); - int uid = mResolveInfo.activityInfo.applicationInfo.uid; + final int uid = mResolveInfo.activityInfo.applicationInfo.uid; + final int userId = UserHandle.myUserId(); boolean alwaysUse = mAlwaysUse.isChecked(); Intent intent = null; @@ -129,9 +127,10 @@ public class UsbConfirmActivity extends AlertActivity service.grantDevicePermission(mDevice, uid); // set or clear default setting if (alwaysUse) { - service.setDevicePackage(mDevice, mResolveInfo.activityInfo.packageName); + service.setDevicePackage( + mDevice, mResolveInfo.activityInfo.packageName, userId); } else { - service.setDevicePackage(mDevice, null); + service.setDevicePackage(mDevice, null, userId); } } else if (mAccessory != null) { intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); @@ -141,10 +140,10 @@ public class UsbConfirmActivity extends AlertActivity service.grantAccessoryPermission(mAccessory, uid); // set or clear default setting if (alwaysUse) { - service.setAccessoryPackage(mAccessory, - mResolveInfo.activityInfo.packageName); + service.setAccessoryPackage( + mAccessory, mResolveInfo.activityInfo.packageName, userId); } else { - service.setAccessoryPackage(mAccessory, null); + service.setAccessoryPackage(mAccessory, null, userId); } } @@ -152,7 +151,7 @@ public class UsbConfirmActivity extends AlertActivity intent.setComponent( new ComponentName(mResolveInfo.activityInfo.packageName, mResolveInfo.activityInfo.name)); - startActivity(intent); + startActivityAsUser(intent, new UserHandle(userId)); } catch (Exception e) { Log.e(TAG, "Unable to start activity", e); } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index c384f5023ce3..6e88d0d91c7f 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -32,6 +32,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -67,7 +68,7 @@ public class UsbPermissionActivity extends AlertActivity mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); mPendingIntent = (PendingIntent)intent.getParcelableExtra(Intent.EXTRA_INTENT); - mUid = intent.getIntExtra("uid", 0); + mUid = intent.getIntExtra(Intent.EXTRA_UID, -1); mPackageName = intent.getStringExtra("package"); PackageManager packageManager = getPackageManager(); @@ -128,7 +129,8 @@ public class UsbPermissionActivity extends AlertActivity if (mPermissionGranted) { service.grantDevicePermission(mDevice, mUid); if (mAlwaysUse.isChecked()) { - service.setDevicePackage(mDevice, mPackageName); + final int userId = UserHandle.getUserId(mUid); + service.setDevicePackage(mDevice, mPackageName, userId); } } } @@ -137,7 +139,8 @@ public class UsbPermissionActivity extends AlertActivity if (mPermissionGranted) { service.grantAccessoryPermission(mAccessory, mUid); if (mAlwaysUse.isChecked()) { - service.setAccessoryPackage(mAccessory, mPackageName); + final int userId = UserHandle.getUserId(mUid); + service.setAccessoryPackage(mAccessory, mPackageName, userId); } } } diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java index f61ecb10318c..9928f7f60306 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java @@ -16,8 +16,6 @@ package com.android.systemui.usb; -import com.android.internal.app.ResolverActivity; - import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.ResolveInfo; @@ -30,9 +28,11 @@ import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Log; import android.widget.CheckBox; +import com.android.internal.app.ResolverActivity; import com.android.systemui.R; import java.util.ArrayList; @@ -92,34 +92,36 @@ public class UsbResolverActivity extends ResolverActivity { super.onDestroy(); } + @Override protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) { try { IBinder b = ServiceManager.getService(USB_SERVICE); IUsbManager service = IUsbManager.Stub.asInterface(b); - int uid = ri.activityInfo.applicationInfo.uid; + final int uid = ri.activityInfo.applicationInfo.uid; + final int userId = UserHandle.myUserId(); if (mDevice != null) { // grant permission for the device service.grantDevicePermission(mDevice, uid); // set or clear default setting if (alwaysCheck) { - service.setDevicePackage(mDevice, ri.activityInfo.packageName); + service.setDevicePackage(mDevice, ri.activityInfo.packageName, userId); } else { - service.setDevicePackage(mDevice, null); + service.setDevicePackage(mDevice, null, userId); } } else if (mAccessory != null) { // grant permission for the accessory service.grantAccessoryPermission(mAccessory, uid); // set or clear default setting if (alwaysCheck) { - service.setAccessoryPackage(mAccessory, ri.activityInfo.packageName); + service.setAccessoryPackage(mAccessory, ri.activityInfo.packageName, userId); } else { - service.setAccessoryPackage(mAccessory, null); + service.setAccessoryPackage(mAccessory, null, userId); } } try { - startActivity(intent); + startActivityAsUser(intent, new UserHandle(userId)); } catch (ActivityNotFoundException e) { Log.e(TAG, "startActivity failed", e); } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 5af32e9b03f8..ba76bcd79731 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -2826,7 +2826,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Dock windows carve out the bottom of the screen, so normal windows // can't appear underneath them. - if (attrs.type == TYPE_INPUT_METHOD && !win.getGivenInsetsPendingLw()) { + if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleOrBehindKeyguardLw() + && !win.getGivenInsetsPendingLw()) { setLastInputMethodWindowLw(null, null); offsetInputMethodWindowLw(win); } diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java index 8e7e07f7b9d6..3df9bb2998da 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java @@ -724,6 +724,7 @@ public class KeyguardHostView extends KeyguardViewBase { inflateAndAddUserSelectorWidgetIfNecessary(); // Add status widget + View statusView = null; int statusWidgetId = mLockPatternUtils.getStatusWidget(); if (statusWidgetId != -1) { addWidget(statusWidgetId); @@ -737,6 +738,16 @@ public class KeyguardHostView extends KeyguardViewBase { mAppWidgetContainer.removeView(newStatusWidget); newStatusWidget.setId(R.id.keyguard_status_view); mAppWidgetContainer.addView(newStatusWidget, oldStatusWidgetPosition); + statusView = newStatusWidget; + } else { + statusView = findViewById(R.id.keyguard_status_view); + } + + // Disable all user interaction on status view. This is done to prevent falsing in the + // pocket from triggering useractivity and prevents 3rd party replacement widgets + // from responding to user interaction while in this position. + if (statusView instanceof KeyguardWidgetFrame) { + ((KeyguardWidgetFrame) statusView).setDisableUserInteraction(true); } // Add user-selected widget @@ -820,4 +831,8 @@ public class KeyguardHostView extends KeyguardViewBase { } } + public void goToUserSwitcher() { + mAppWidgetContainer.setCurrentPage(getWidgetPosition(R.id.keyguard_multi_user_selector)); + } + } diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java index 8c1dfe19977b..a034f4c52977 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardMultiUserAvatar.java @@ -22,11 +22,11 @@ import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.LayoutInflater; -import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; @@ -38,9 +38,13 @@ class KeyguardMultiUserAvatar extends FrameLayout { private ImageView mUserImage; private TextView mUserName; private UserInfo mUserInfo; - private static final int INACTIVE_COLOR = 85; - private static final int INACTIVE_ALPHA = 195; - private static final float ACTIVE_SCALE = 1.1f; + private static final float ACTIVE_ALPHA = 1.0f; + private static final float INACTIVE_ALPHA = 0.5f; + private static final float ACTIVE_SCALE = 1.2f; + private static final float ACTIVE_TEXT_BACGROUND_ALPHA = 0.5f; + private static final float INACTIVE_TEXT_BACGROUND_ALPHA = 0f; + private static int mActiveTextColor; + private static int mInactiveTextColor; private boolean mActive; private boolean mInit = true; private KeyguardMultiUserSelectorView mUserSelector; @@ -76,8 +80,12 @@ class KeyguardMultiUserAvatar extends FrameLayout { } private void init() { + Resources res = mContext.getResources(); + mActiveTextColor = res.getColor(R.color.kg_multi_user_text_active); + mInactiveTextColor = res.getColor(R.color.kg_multi_user_text_inactive); + mUserImage = (ImageView) findViewById(R.id.keyguard_user_avatar); - mUserName = (TextView) findViewById(R.id.keyguard_user_name); + mUserName = (TextView) findViewById(R.id.keyguard_user_name); mUserImage.setImageDrawable(Drawable.createFromPath(mUserInfo.iconPath)); mUserName.setText(mUserInfo.name); @@ -89,51 +97,61 @@ class KeyguardMultiUserAvatar extends FrameLayout { public void setActive(boolean active, boolean animate, int duration, final Runnable onComplete) { if (mActive != active || mInit) { mActive = active; - final int finalFilterAlpha = mActive ? 0 : INACTIVE_ALPHA; - final int initFilterAlpha = mActive ? INACTIVE_ALPHA : 0; - - final float finalScale = mActive ? ACTIVE_SCALE : 1.0f; - final float initScale = mActive ? 1.0f : ACTIVE_SCALE; if (active) { KeyguardSubdivisionLayout parent = (KeyguardSubdivisionLayout) getParent(); parent.setTopChild(parent.indexOfChild(this)); } + } + updateVisualsForActive(mActive, animate, duration, true, onComplete); + } - if (animate) { - ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); - va.addUpdateListener(new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float r = animation.getAnimatedFraction(); - float scale = (1 - r) * initScale + r * finalScale; - int filterAlpha = (int) ((1 - r) * initFilterAlpha + r * finalFilterAlpha); - setScaleX(scale); - setScaleY(scale); - mUserImage.setColorFilter(Color.argb(filterAlpha, INACTIVE_COLOR, - INACTIVE_COLOR, INACTIVE_COLOR)); - mUserSelector.invalidate(); - - } - }); - va.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (onComplete != null) { - onComplete.run(); - } + void updateVisualsForActive(boolean active, boolean animate, int duration, boolean scale, + final Runnable onComplete) { + final float finalAlpha = active ? ACTIVE_ALPHA : INACTIVE_ALPHA; + final float initAlpha = active ? INACTIVE_ALPHA : ACTIVE_ALPHA; + final float finalScale = active && scale ? ACTIVE_SCALE : 1.0f; + final float initScale = active ? 1.0f : ACTIVE_SCALE; + final int finalTextBgAlpha = active ? (int) (ACTIVE_TEXT_BACGROUND_ALPHA * 255) : + (int) (INACTIVE_TEXT_BACGROUND_ALPHA * 255); + final int initTextBgAlpha = active ? (int) (INACTIVE_TEXT_BACGROUND_ALPHA * 255) : + (int) (ACTIVE_TEXT_BACGROUND_ALPHA * 255); + int textColor = active ? mActiveTextColor : mInactiveTextColor; + mUserName.setTextColor(textColor); + + if (animate) { + ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); + va.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float r = animation.getAnimatedFraction(); + float scale = (1 - r) * initScale + r * finalScale; + float alpha = (1 - r) * initAlpha + r * finalAlpha; + int textBgAlpha = (int) ((1 - r) * initTextBgAlpha + r * finalTextBgAlpha); + setScaleX(scale); + setScaleY(scale); + mUserImage.setAlpha(alpha); + mUserName.setBackgroundColor(Color.argb(textBgAlpha, 0, 0, 0)); + mUserSelector.invalidate(); + } + }); + va.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (onComplete != null) { + onComplete.run(); } - }); - va.setDuration(duration); - va.start(); - } else { - setScaleX(finalScale); - setScaleY(finalScale); - mUserImage.setColorFilter(Color.argb(finalFilterAlpha, INACTIVE_COLOR, - INACTIVE_COLOR, INACTIVE_COLOR)); - if (onComplete != null) { - post(onComplete); } + }); + va.setDuration(duration); + va.start(); + } else { + setScaleX(finalScale); + setScaleY(finalScale); + mUserImage.setAlpha(finalAlpha); + mUserName.setBackgroundColor(Color.argb(finalTextBgAlpha, 0, 0, 0)); + if (onComplete != null) { + post(onComplete); } } } @@ -156,13 +174,7 @@ class KeyguardMultiUserAvatar extends FrameLayout { public void setPressed(boolean pressed) { if (!mPressedStateLocked) { super.setPressed(pressed); - if (pressed) { - mUserImage.setColorFilter(Color.argb(0, INACTIVE_COLOR, - INACTIVE_COLOR, INACTIVE_COLOR)); - } else if (!mActive) { - mUserImage.setColorFilter(Color.argb(INACTIVE_ALPHA, INACTIVE_COLOR, - INACTIVE_COLOR, INACTIVE_COLOR)); - } + updateVisualsForActive(pressed || mActive, false, 0, mActive, null); } else { mTempPressedStateHolder = pressed; } diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java index a3a9c5f56819..c4f761f8456c 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java @@ -24,9 +24,11 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.IBinder; +import android.os.Parcelable; import android.os.SystemProperties; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -89,7 +91,7 @@ public class KeyguardViewManager { boolean enableScreenRotation = shouldEnableScreenRotation(); - maybeCreateKeyguardLocked(enableScreenRotation); + maybeCreateKeyguardLocked(enableScreenRotation, false); maybeEnableScreenRotation(enableScreenRotation); // Disable common aspects of the system/status/navigation bars that are not appropriate or @@ -120,13 +122,19 @@ public class KeyguardViewManager { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - maybeCreateKeyguardLocked(shouldEnableScreenRotation()); + maybeCreateKeyguardLocked(shouldEnableScreenRotation(), false); } } - private void maybeCreateKeyguardLocked(boolean enableScreenRotation) { + SparseArray<Parcelable> mStateContainer = new SparseArray<Parcelable>(); + + private void maybeCreateKeyguardLocked(boolean enableScreenRotation, boolean userSwitched) { final boolean isActivity = (mContext instanceof Activity); // for test activity + if (mKeyguardHost != null) { + mKeyguardHost.saveHierarchyState(mStateContainer); + } + if (mKeyguardHost == null) { if (DEBUG) Log.d(TAG, "keyguard host is null, creating it..."); @@ -161,11 +169,13 @@ public class KeyguardViewManager { mWindowLayoutParams = lp; mViewManager.addView(mKeyguardHost, lp); } - inflateKeyguardView(); + inflateKeyguardView(userSwitched); mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); + + mKeyguardHost.restoreHierarchyState(mStateContainer); } - private void inflateKeyguardView() { + private void inflateKeyguardView(boolean userSwitched) { View v = mKeyguardHost.findViewById(R.id.keyguard_host_view); if (v != null) { mKeyguardHost.removeView(v); @@ -179,6 +189,10 @@ public class KeyguardViewManager { mKeyguardView.setLockPatternUtils(mLockPatternUtils); mKeyguardView.setViewMediatorCallback(mViewMediatorCallback); + if (userSwitched) { + mKeyguardView.goToUserSwitcher(); + } + if (mScreenOn) { mKeyguardView.show(); } @@ -219,11 +233,11 @@ public class KeyguardViewManager { /** * Reset the state of the view. */ - public synchronized void reset() { + public synchronized void reset(boolean userSwitched) { if (DEBUG) Log.d(TAG, "reset()"); // User might have switched, check if we need to go back to keyguard // TODO: It's preferable to stay and show the correct lockscreen or unlock if none - maybeCreateKeyguardLocked(shouldEnableScreenRotation()); + maybeCreateKeyguardLocked(shouldEnableScreenRotation(), userSwitched); } public synchronized void onScreenTurnedOff() { diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java index 59aed2432322..83324bc4e715 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewMediator.java @@ -291,7 +291,7 @@ public class KeyguardViewMediator { public void onUserSwitched(int userId) { // Note that the mLockPatternUtils user has already been updated from setCurrentUser. synchronized (KeyguardViewMediator.this) { - resetStateLocked(); + resetStateLocked(true); } // We should always go back to the locked state when a user // switch happens. Is there a more direct way to do this? @@ -351,7 +351,7 @@ public class KeyguardViewMediator { + "device isn't provisioned yet."); doKeyguardLocked(); } else { - resetStateLocked(); + resetStateLocked(false); } } } @@ -364,7 +364,7 @@ public class KeyguardViewMediator { + "showing; need to show keyguard so user can enter sim pin"); doKeyguardLocked(); } else { - resetStateLocked(); + resetStateLocked(false); } } break; @@ -377,14 +377,14 @@ public class KeyguardViewMediator { } else { if (DEBUG) Log.d(TAG, "PERM_DISABLED, resetStateLocked to" + "show permanently disabled message in lockscreen."); - resetStateLocked(); + resetStateLocked(false); } } break; case READY: synchronized (this) { if (isShowing()) { - resetStateLocked(); + resetStateLocked(false); } } break; @@ -530,7 +530,7 @@ public class KeyguardViewMediator { } } else if (mShowing) { notifyScreenOffLocked(); - resetStateLocked(); + resetStateLocked(false); } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) { // if the screen turned off because of timeout or the user hit the power button @@ -644,7 +644,7 @@ public class KeyguardViewMediator { if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting"); mExitSecureCallback.onKeyguardExitResult(false); mExitSecureCallback = null; - resetStateLocked(); + resetStateLocked(false); } else { showLocked(); @@ -805,11 +805,12 @@ public class KeyguardViewMediator { /** * Send message to keyguard telling it to reset its state. + * @param userSwitched true if we're resetting state because user switched * @see #handleReset() */ - private void resetStateLocked() { + private void resetStateLocked(boolean userSwitched) { if (DEBUG) Log.d(TAG, "resetStateLocked"); - Message msg = mHandler.obtainMessage(RESET); + Message msg = mHandler.obtainMessage(RESET, userSwitched ? 1 : 0, 0); mHandler.sendMessage(msg); } @@ -1046,7 +1047,7 @@ public class KeyguardViewMediator { handleHide(); return ; case RESET: - handleReset(); + handleReset(msg.arg1 != 0); return ; case VERIFY_UNLOCK: handleVerifyUnlock(); @@ -1289,13 +1290,13 @@ public class KeyguardViewMediator { } /** - * Handle message sent by {@link #resetStateLocked()} + * Handle message sent by {@link #resetStateLocked(boolean)} * @see #RESET */ - private void handleReset() { + private void handleReset(boolean userSwitched) { synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleReset"); - mKeyguardViewManager.reset(); + mKeyguardViewManager.reset(userSwitched); } } diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java index d17c12832969..9d9b0431a176 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java @@ -19,13 +19,17 @@ package com.android.internal.policy.impl.keyguard; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.NinePatchDrawable; +import android.graphics.Shader; +import android.os.PowerManager; +import android.os.SystemClock; import android.util.AttributeSet; +import android.view.MotionEvent; import android.widget.FrameLayout; import com.android.internal.R; @@ -33,13 +37,19 @@ import com.android.internal.R; public class KeyguardWidgetFrame extends FrameLayout { private final static PorterDuffXfermode sAddBlendMode = new PorterDuffXfermode(PorterDuff.Mode.ADD); - private static Drawable sLeftOverscrollDrawable; - private static Drawable sRightOverscrollDrawable; - private Drawable mForegroundDrawable; + private int mGradientColor; + private LinearGradient mForegroundGradient; + private LinearGradient mLeftToRightGradient; + private LinearGradient mRightToLeftGradient; + private Paint mGradientPaint = new Paint(); + boolean mLeftToRight = true; + private float mOverScrollAmount = 0f; private final Rect mForegroundRect = new Rect(); private int mForegroundAlpha = 0; + private PowerManager mPowerManager; + private boolean mDisableInteraction; public KeyguardWidgetFrame(Context context) { this(context, null, 0); @@ -51,28 +61,42 @@ public class KeyguardWidgetFrame extends FrameLayout { public KeyguardWidgetFrame(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - Resources res = context.getResources(); - if (sLeftOverscrollDrawable == null) { - sLeftOverscrollDrawable = res.getDrawable(R.drawable.kg_widget_overscroll_layer_left); - sRightOverscrollDrawable = res.getDrawable(R.drawable.kg_widget_overscroll_layer_right); - } + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + + Resources res = context.getResources(); int hPadding = res.getDimensionPixelSize(R.dimen.kg_widget_pager_horizontal_padding); int topPadding = res.getDimensionPixelSize(R.dimen.kg_widget_pager_top_padding); int bottomPadding = res.getDimensionPixelSize(R.dimen.kg_widget_pager_bottom_padding); setPadding(hPadding, topPadding, hPadding, bottomPadding); + mGradientColor = res.getColor(R.color.kg_widget_pager_gradient); + mGradientPaint.setXfermode(sAddBlendMode); + } + + public void setDisableUserInteraction(boolean disabled) { + mDisableInteraction = disabled; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (!mDisableInteraction) { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + return super.onInterceptTouchEvent(ev); + } + return true; } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - if (mForegroundAlpha > 0) { - mForegroundDrawable.setBounds(mForegroundRect); - Paint p = ((NinePatchDrawable) mForegroundDrawable).getPaint(); - p.setXfermode(sAddBlendMode); - mForegroundDrawable.draw(canvas); - p.setXfermode(null); - } + drawGradientOverlay(canvas); + + } + + private void drawGradientOverlay(Canvas c) { + mGradientPaint.setShader(mForegroundGradient); + mGradientPaint.setAlpha(mForegroundAlpha); + c.drawRect(mForegroundRect, mGradientPaint); } @Override @@ -80,27 +104,20 @@ public class KeyguardWidgetFrame extends FrameLayout { super.onSizeChanged(w, h, oldw, oldh); mForegroundRect.set(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - getPaddingBottom()); + float x0 = mLeftToRight ? 0 : mForegroundRect.width(); + float x1 = mLeftToRight ? mForegroundRect.width(): 0; + mLeftToRightGradient = new LinearGradient(x0, 0f, x1, 0f, + mGradientColor, 0, Shader.TileMode.CLAMP); + mRightToLeftGradient = new LinearGradient(x1, 0f, x0, 0f, + mGradientColor, 0, Shader.TileMode.CLAMP); } void setOverScrollAmount(float r, boolean left) { if (Float.compare(mOverScrollAmount, r) != 0) { mOverScrollAmount = r; - if (left && mForegroundDrawable != sLeftOverscrollDrawable) { - mForegroundDrawable = sLeftOverscrollDrawable; - } else if (!left && mForegroundDrawable != sRightOverscrollDrawable) { - mForegroundDrawable = sRightOverscrollDrawable; - } - - mForegroundAlpha = (int) Math.round((r * 255)); - mForegroundDrawable.setAlpha(mForegroundAlpha); - if (getLayerType() != LAYER_TYPE_HARDWARE) { - setLayerType(LAYER_TYPE_HARDWARE, null); - } + mForegroundGradient = left ? mLeftToRightGradient : mRightToLeftGradient; + mForegroundAlpha = (int) Math.round((0.85f * r * 255)); invalidate(); - } else { - if (getLayerType() != LAYER_TYPE_NONE) { - setLayerType(LAYER_TYPE_NONE, null); - } } } } diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java index a1603dcda961..9dfbba809683 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java @@ -76,6 +76,25 @@ public class KeyguardWidgetPager extends PagedView { } } + @Override + protected void onPageBeginMoving() { + // Enable hardware layers while pages are moving + // TODO: We should only do this for the two views that are actually moving + int children = getChildCount(); + for (int i = 0; i < children; i++) { + getChildAt(i).setLayerType(LAYER_TYPE_HARDWARE, null); + } + } + + @Override + protected void onPageEndMoving() { + // Disable hardware layers while pages are moving + int children = getChildCount(); + for (int i = 0; i < children; i++) { + getChildAt(i).setLayerType(LAYER_TYPE_NONE, null); + } + } + /* * This interpolator emulates the rate at which the perceived scale of an object changes * as its distance from a camera increases. When this interpolator is applied to a scale diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetRegion.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetRegion.java index f7f23c714528..e9ea2c37b22b 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetRegion.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetRegion.java @@ -16,6 +16,8 @@ package com.android.internal.policy.impl.keyguard; import android.content.Context; +import android.os.PowerManager; +import android.os.SystemClock; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -29,6 +31,7 @@ public class KeyguardWidgetRegion extends LinearLayout implements PageSwitchList KeyguardGlowStripView mRightStrip; KeyguardWidgetPager mPager; private int mPage = 0; + private PowerManager mPowerManager; public KeyguardWidgetRegion(Context context) { this(context, null, 0); @@ -40,6 +43,7 @@ public class KeyguardWidgetRegion extends LinearLayout implements PageSwitchList public KeyguardWidgetRegion(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); } @Override @@ -70,21 +74,25 @@ public class KeyguardWidgetRegion extends LinearLayout implements PageSwitchList @Override public void onPageSwitch(View newPage, int newPageIndex) { - mPage = newPageIndex; - - // If we're showing the default system status widget, then we want to hide the clock - boolean hideClock = false; + boolean showingStatusWidget = false; if ((newPage instanceof ViewGroup)) { ViewGroup vg = (ViewGroup) newPage; if (vg.getChildAt(0) instanceof KeyguardStatusView) { - hideClock = true; + showingStatusWidget = true; } } - if (hideClock) { + // Disable the status bar clock if we're showing the default status widget + if (showingStatusWidget) { setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK); } else { setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK); } + + // Extend the display timeout if the user switches pages + if (mPage != newPageIndex) { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + mPage = newPageIndex; + } } } diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index 0b4871df4e49..0045f4a8d97f 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -40,11 +40,9 @@ import android.util.Slog; import java.io.File; import java.io.FileDescriptor; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.util.Arrays; /** @@ -69,12 +67,12 @@ import java.util.Arrays; * a degree Centigrade</p> * <p>"technology" - String, the type of battery installed, e.g. "Li-ion"</p> */ -public class BatteryService extends Binder { +public final class BatteryService extends Binder { private static final String TAG = BatteryService.class.getSimpleName(); - private static final boolean LOCAL_LOGV = false; + private static final boolean DEBUG = false; - static final int BATTERY_SCALE = 100; // battery capacity is a percentage + private static final int BATTERY_SCALE = 100; // battery capacity is a percentage // Used locally for determining when to make a last ditch effort to log // discharge stats before the device dies. @@ -92,6 +90,9 @@ public class BatteryService extends Binder { private final Context mContext; private final IBatteryStats mBatteryStats; + private final Object mLock = new Object(); + + /* Begin native fields: All of these fields are set by native code. */ private boolean mAcOnline; private boolean mUsbOnline; private boolean mWirelessOnline; @@ -103,7 +104,7 @@ public class BatteryService extends Binder { private int mBatteryTemperature; private String mBatteryTechnology; private boolean mBatteryLevelCritical; - private int mInvalidCharger; + /* End native fields. */ private int mLastBatteryStatus; private int mLastBatteryHealth; @@ -112,6 +113,8 @@ public class BatteryService extends Binder { private int mLastBatteryVoltage; private int mLastBatteryTemperature; private boolean mLastBatteryLevelCritical; + + private int mInvalidCharger; private int mLastInvalidCharger; private int mLowBatteryWarningLevel; @@ -128,6 +131,8 @@ public class BatteryService extends Binder { private boolean mSentLowBatteryBroadcast = false; + private native void native_update(); + public BatteryService(Context context, LightsService lights) { mContext = context; mLed = new Led(context, lights); @@ -146,83 +151,83 @@ public class BatteryService extends Binder { // watch for invalid charger messages if the invalid_charger switch exists if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) { - mInvalidChargerObserver.startObserving("DEVPATH=/devices/virtual/switch/invalid_charger"); + mInvalidChargerObserver.startObserving( + "DEVPATH=/devices/virtual/switch/invalid_charger"); } // set initial status - update(); + synchronized (mLock) { + updateLocked(); + } } - public final boolean isPowered() { - // assume we are powered if battery state is unknown so the "stay on while plugged in" option will work. - return (mAcOnline || mUsbOnline || mWirelessOnline - || mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN); + void systemReady() { + // check our power situation now that it is safe to display the shutdown dialog. + synchronized (mLock) { + shutdownIfNoPowerLocked(); + shutdownIfOverTempLocked(); + } } - public final boolean isPowered(int plugTypeSet) { + /** + * Returns true if the device is plugged into any of the specified plug types. + */ + public boolean isPowered(int plugTypeSet) { + synchronized (mLock) { + return isPoweredLocked(plugTypeSet); + } + } + + private boolean isPoweredLocked(int plugTypeSet) { // assume we are powered if battery state is unknown so // the "stay on while plugged in" option will work. if (mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) { return true; } - if (plugTypeSet == 0) { - return false; - } - int plugTypeBit = 0; - if (mAcOnline) { - plugTypeBit |= BatteryManager.BATTERY_PLUGGED_AC; + if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mAcOnline) { + return true; } - if (mUsbOnline) { - plugTypeBit |= BatteryManager.BATTERY_PLUGGED_USB; + if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mUsbOnline) { + return true; } - if (mWirelessOnline) { - plugTypeBit |= BatteryManager.BATTERY_PLUGGED_WIRELESS; + if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mWirelessOnline) { + return true; } - return (plugTypeSet & plugTypeBit) != 0; + return false; } - public final int getPlugType() { - return mPlugType; - } - - private UEventObserver mPowerSupplyObserver = new UEventObserver() { - @Override - public void onUEvent(UEventObserver.UEvent event) { - update(); - } - }; - - private UEventObserver mInvalidChargerObserver = new UEventObserver() { - @Override - public void onUEvent(UEventObserver.UEvent event) { - int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0; - if (mInvalidCharger != invalidCharger) { - mInvalidCharger = invalidCharger; - update(); - } + /** + * Returns the current plug type. + */ + public int getPlugType() { + synchronized (mLock) { + return mPlugType; } - }; - - // returns battery level as a percentage - public final int getBatteryLevel() { - return mBatteryLevel; } - // true if battery level is below the first warning threshold - public final boolean isBatteryLow() { - return mBatteryPresent && mBatteryLevel <= mLowBatteryWarningLevel; + /** + * Returns battery level as a percentage. + */ + public int getBatteryLevel() { + synchronized (mLock) { + return mBatteryLevel; + } } - void systemReady() { - // check our power situation now that it is safe to display the shutdown dialog. - shutdownIfNoPower(); - shutdownIfOverTemp(); + /** + * Returns true if battery level is below the first warning threshold. + */ + public boolean isBatteryLow() { + synchronized (mLock) { + return mBatteryPresent && mBatteryLevel <= mLowBatteryWarningLevel; + } } - private final void shutdownIfNoPower() { + private void shutdownIfNoPowerLocked() { // shut down gracefully if our battery is critically low and we are not powered. // wait until the system has booted before attempting to display the shutdown dialog. - if (mBatteryLevel == 0 && !isPowered() && ActivityManagerNative.isSystemReady()) { + if (mBatteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY) + && ActivityManagerNative.isSystemReady()) { Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -230,7 +235,7 @@ public class BatteryService extends Binder { } } - private final void shutdownIfOverTemp() { + private void shutdownIfOverTempLocked() { // shut down gracefully if temperature is too high (> 68.0C by default) // wait until the system has booted before attempting to display the // shutdown dialog. @@ -243,18 +248,19 @@ public class BatteryService extends Binder { } } - private native void native_update(); - - private synchronized final void update() { + private void updateLocked() { + // Update the values of mAcOnline, et. all. native_update(); - processValues(); + + // Process the new values. + processValuesLocked(); } - private void processValues() { + private void processValuesLocked() { boolean logOutlier = false; long dischargeDuration = 0; - mBatteryLevelCritical = mBatteryLevel <= mCriticalBatteryLevel; + mBatteryLevelCritical = (mBatteryLevel <= mCriticalBatteryLevel); if (mAcOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_AC; } else if (mUsbOnline) { @@ -265,6 +271,22 @@ public class BatteryService extends Binder { mPlugType = BATTERY_PLUGGED_NONE; } + if (DEBUG) { + Slog.d(TAG, "Processing new values: " + + "mAcOnline=" + mAcOnline + + ", mUsbOnline=" + mUsbOnline + + ", mWirelessOnline=" + mWirelessOnline + + ", mBatteryStatus=" + mBatteryStatus + + ", mBatteryHealth=" + mBatteryHealth + + ", mBatteryPresent=" + mBatteryPresent + + ", mBatteryLevel=" + mBatteryLevel + + ", mBatteryTechnology=" + mBatteryTechnology + + ", mBatteryVoltage=" + mBatteryVoltage + + ", mBatteryTemperature=" + mBatteryTemperature + + ", mBatteryLevelCritical=" + mBatteryLevelCritical + + ", mPlugType=" + mPlugType); + } + // Let the battery stats keep track of the current level. try { mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth, @@ -274,8 +296,8 @@ public class BatteryService extends Binder { // Should never happen. } - shutdownIfNoPower(); - shutdownIfOverTemp(); + shutdownIfNoPowerLocked(); + shutdownIfOverTempLocked(); if (mBatteryStatus != mLastBatteryStatus || mBatteryHealth != mLastBatteryHealth || @@ -342,7 +364,7 @@ public class BatteryService extends Binder { && mBatteryLevel <= mLowBatteryWarningLevel && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); - sendIntent(); + sendIntentLocked(); // Separate broadcast is sent for power connected / not connected // since the standard intent will not wake any applications and some @@ -373,7 +395,7 @@ public class BatteryService extends Binder { // This needs to be done after sendIntent() so that we get the lastest battery stats. if (logOutlier && dischargeDuration != 0) { - logOutlier(dischargeDuration); + logOutlierLocked(dischargeDuration); } mLastBatteryStatus = mBatteryStatus; @@ -388,13 +410,13 @@ public class BatteryService extends Binder { } } - private final void sendIntent() { + private void sendIntentLocked() { // Pack up the values and broadcast them to everyone Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING); - int icon = getIcon(mBatteryLevel); + int icon = getIconLocked(mBatteryLevel); intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryStatus); intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryHealth); @@ -408,22 +430,22 @@ public class BatteryService extends Binder { intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology); intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); - if (false) { - Slog.d(TAG, "level:" + mBatteryLevel + - " scale:" + BATTERY_SCALE + " status:" + mBatteryStatus + - " health:" + mBatteryHealth + " present:" + mBatteryPresent + - " voltage: " + mBatteryVoltage + - " temperature: " + mBatteryTemperature + - " technology: " + mBatteryTechnology + - " AC powered:" + mAcOnline + " USB powered:" + mUsbOnline + - " Wireless powered:" + mWirelessOnline + - " icon:" + icon + " invalid charger:" + mInvalidCharger); + if (DEBUG) { + Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. level:" + mBatteryLevel + + ", scale:" + BATTERY_SCALE + ", status:" + mBatteryStatus + + ", health:" + mBatteryHealth + ", present:" + mBatteryPresent + + ", voltage: " + mBatteryVoltage + + ", temperature: " + mBatteryTemperature + + ", technology: " + mBatteryTechnology + + ", AC powered:" + mAcOnline + ", USB powered:" + mUsbOnline + + ", Wireless powered:" + mWirelessOnline + + ", icon:" + icon + ", invalid charger:" + mInvalidCharger); } ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL); } - private final void logBatteryStats() { + private void logBatteryStatsLocked() { IBinder batteryInfoService = ServiceManager.getService(BATTERY_STATS_SERVICE_NAME); if (batteryInfoService == null) return; @@ -461,7 +483,7 @@ public class BatteryService extends Binder { } } - private final void logOutlier(long duration) { + private void logOutlierLocked(long duration) { ContentResolver cr = mContext.getContentResolver(); String dischargeThresholdString = Settings.Global.getString(cr, Settings.Global.BATTERY_DISCHARGE_THRESHOLD); @@ -475,11 +497,11 @@ public class BatteryService extends Binder { if (duration <= durationThreshold && mDischargeStartLevel - mBatteryLevel >= dischargeThreshold) { // If the discharge cycle is bad enough we want to know about it. - logBatteryStats(); + logBatteryStatsLocked(); } - if (LOCAL_LOGV) Slog.v(TAG, "duration threshold: " + durationThreshold + + if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold + " discharge threshold: " + dischargeThreshold); - if (LOCAL_LOGV) Slog.v(TAG, "duration: " + duration + " discharge: " + + if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " + (mDischargeStartLevel - mBatteryLevel)); } catch (NumberFormatException e) { Slog.e(TAG, "Invalid DischargeThresholds GService string: " + @@ -489,14 +511,15 @@ public class BatteryService extends Binder { } } - private final int getIcon(int level) { + private int getIconLocked(int level) { if (mBatteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) { return com.android.internal.R.drawable.stat_sys_battery_charge; } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) { return com.android.internal.R.drawable.stat_sys_battery; } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING || mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) { - if (isPowered() && mBatteryLevel >= 100) { + if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY) + && mBatteryLevel >= 100) { return com.android.internal.R.drawable.stat_sys_battery_charge; } else { return com.android.internal.R.drawable.stat_sys_battery; @@ -517,8 +540,8 @@ public class BatteryService extends Binder { return; } - if (args == null || args.length == 0 || "-a".equals(args[0])) { - synchronized (this) { + synchronized (mLock) { + if (args == null || args.length == 0 || "-a".equals(args[0])) { pw.println("Current Battery Service state:"); pw.println(" AC powered: " + mAcOnline); pw.println(" USB powered: " + mUsbOnline); @@ -531,73 +554,89 @@ public class BatteryService extends Binder { pw.println(" voltage:" + mBatteryVoltage); pw.println(" temperature: " + mBatteryTemperature); pw.println(" technology: " + mBatteryTechnology); - } - } else if (false) { - // DO NOT SUBMIT WITH THIS TURNED ON - if (args.length == 3 && "set".equals(args[0])) { - String key = args[1]; - String value = args[2]; - try { - boolean update = true; - if ("ac".equals(key)) { - mAcOnline = Integer.parseInt(value) != 0; - } else if ("usb".equals(key)) { - mUsbOnline = Integer.parseInt(value) != 0; - } else if ("wireless".equals(key)) { - mWirelessOnline = Integer.parseInt(value) != 0; - } else if ("status".equals(key)) { - mBatteryStatus = Integer.parseInt(value); - } else if ("level".equals(key)) { - mBatteryLevel = Integer.parseInt(value); - } else if ("invalid".equals(key)) { - mInvalidCharger = Integer.parseInt(value); - } else { - update = false; + } else if (false) { + // DO NOT SUBMIT WITH THIS TURNED ON + if (args.length == 3 && "set".equals(args[0])) { + String key = args[1]; + String value = args[2]; + try { + boolean update = true; + if ("ac".equals(key)) { + mAcOnline = Integer.parseInt(value) != 0; + } else if ("usb".equals(key)) { + mUsbOnline = Integer.parseInt(value) != 0; + } else if ("wireless".equals(key)) { + mWirelessOnline = Integer.parseInt(value) != 0; + } else if ("status".equals(key)) { + mBatteryStatus = Integer.parseInt(value); + } else if ("level".equals(key)) { + mBatteryLevel = Integer.parseInt(value); + } else if ("invalid".equals(key)) { + mInvalidCharger = Integer.parseInt(value); + } else { + update = false; + } + if (update) { + processValuesLocked(); + } + } catch (NumberFormatException ex) { + pw.println("Bad value: " + value); } - if (update) { - processValues(); - } - } catch (NumberFormatException ex) { - pw.println("Bad value: " + value); } } } } - class Led { - private LightsService mLightsService; - private LightsService.Light mBatteryLight; + private final UEventObserver mPowerSupplyObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + synchronized (mLock) { + updateLocked(); + } + } + }; + + private final UEventObserver mInvalidChargerObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0; + synchronized (mLock) { + if (mInvalidCharger != invalidCharger) { + mInvalidCharger = invalidCharger; + updateLocked(); + } + } + } + }; - private int mBatteryLowARGB; - private int mBatteryMediumARGB; - private int mBatteryFullARGB; - private int mBatteryLedOn; - private int mBatteryLedOff; + private final class Led { + private final LightsService.Light mBatteryLight; - private boolean mBatteryCharging; - private boolean mBatteryLow; - private boolean mBatteryFull; + private final int mBatteryLowARGB; + private final int mBatteryMediumARGB; + private final int mBatteryFullARGB; + private final int mBatteryLedOn; + private final int mBatteryLedOff; - Led(Context context, LightsService lights) { - mLightsService = lights; + public Led(Context context, LightsService lights) { mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); - mBatteryLowARGB = mContext.getResources().getInteger( + mBatteryLowARGB = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryLowARGB); - mBatteryMediumARGB = mContext.getResources().getInteger( + mBatteryMediumARGB = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryMediumARGB); - mBatteryFullARGB = mContext.getResources().getInteger( + mBatteryFullARGB = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryFullARGB); - mBatteryLedOn = mContext.getResources().getInteger( + mBatteryLedOn = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryLedOn); - mBatteryLedOff = mContext.getResources().getInteger( + mBatteryLedOff = context.getResources().getInteger( com.android.internal.R.integer.config_notificationsBatteryLedOff); } /** * Synchronize on BatteryService. */ - void updateLightsLocked() { + public void updateLightsLocked() { final int level = mBatteryLevel; final int status = mBatteryStatus; if (level < mLowBatteryWarningLevel) { diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index a4c376d29ed9..94a087a0c756 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -405,7 +405,7 @@ public class DeviceStorageMonitorService extends Binder { notification.setLatestEventInfo(mContext, title, details, intent); mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification, UserHandle.ALL); - mContext.sendStickyBroadcast(mStorageLowIntent); + mContext.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL); } /** @@ -428,7 +428,7 @@ public class DeviceStorageMonitorService extends Binder { */ private final void sendFullNotification() { if(localLOGV) Slog.i(TAG, "Sending memory full notification"); - mContext.sendStickyBroadcast(mStorageFullIntent); + mContext.sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL); } /** diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 2197e3122269..ae95c4c00882 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -645,12 +645,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run /** * Returns all providers by name, including passive, but excluding - * fused. + * fused, also including ones that are not permitted to + * be accessed by the calling activity or are currently disabled. */ @Override public List<String> getAllProviders() { - checkPermission(); - ArrayList<String> out; synchronized (mLock) { out = new ArrayList<String>(mProviders.size()); diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index 9dbe50352d8e..1342250f48ca 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -26,6 +26,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.BatteryManager; import android.os.Debug; import android.os.Handler; import android.os.Message; @@ -329,7 +330,7 @@ public class Watchdog extends Thread { * text of why it is not a good time. */ String shouldWeBeBrutalLocked(long curTime) { - if (mBattery == null || !mBattery.isPowered()) { + if (mBattery == null || !mBattery.isPowered(BatteryManager.BATTERY_PLUGGED_ANY)) { return "battery"; } diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index 664125ad95d6..d1c24ebd69ea 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -130,11 +130,22 @@ public final class PowerManagerService extends IPowerManager.Stub private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000; private static final int MINIMUM_SCREEN_OFF_TIMEOUT = 10 * 1000; - // The screen dim duration, in seconds. + // The screen dim duration, in milliseconds. // This is subtracted from the end of the screen off timeout so the // minimum screen off timeout should be longer than this. private static final int SCREEN_DIM_DURATION = 7 * 1000; + // The maximum screen dim time expressed as a ratio relative to the screen + // off timeout. If the screen off timeout is very short then we want the + // dim timeout to also be quite short so that most of the time is spent on. + // Otherwise the user won't get much screen on time before dimming occurs. + private static final float MAXIMUM_SCREEN_DIM_RATIO = 0.2f; + + // Upper bound on the battery charge percentage in order to consider turning + // the screen on when the device starts charging wirelessly. + // See point of use for more details. + private static final int WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT = 95; + private Context mContext; private LightsService mLightsService; private BatteryService mBatteryService; @@ -218,6 +229,9 @@ public final class PowerManagerService extends IPowerManager.Stub // True if the device is plugged into a power source. private boolean mIsPowered; + // The current plug type, such as BatteryManager.BATTERY_PLUGGED_WIRELESS. + private int mPlugType; + // True if the device should wake up when plugged or unplugged. private boolean mWakeUpWhenPluggedOrUnpluggedConfig; @@ -1013,10 +1027,19 @@ public final class PowerManagerService extends IPowerManager.Stub */ private void updateIsPoweredLocked(int dirty) { if ((dirty & DIRTY_BATTERY_STATE) != 0) { - boolean wasPowered = mIsPowered; - mIsPowered = mBatteryService.isPowered(); + final boolean wasPowered = mIsPowered; + final int oldPlugType = mPlugType; + mIsPowered = mBatteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); + mPlugType = mBatteryService.getPlugType(); - if (wasPowered != mIsPowered) { + if (DEBUG) { + Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered + + ", mIsPowered=" + mIsPowered + + ", oldPlugType=" + oldPlugType + + ", mPlugType=" + mPlugType); + } + + if (wasPowered != mIsPowered || oldPlugType != mPlugType) { mDirty |= DIRTY_IS_POWERED; // Treat plugging and unplugging the devices as a user activity. @@ -1025,7 +1048,7 @@ public final class PowerManagerService extends IPowerManager.Stub // Some devices also wake the device when plugged or unplugged because // they don't have a charging LED. final long now = SystemClock.uptimeMillis(); - if (mWakeUpWhenPluggedOrUnpluggedConfig) { + if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType)) { wakeUpNoUpdateLocked(now); } userActivityNoUpdateLocked( @@ -1034,6 +1057,44 @@ public final class PowerManagerService extends IPowerManager.Stub } } + private boolean shouldWakeUpWhenPluggedOrUnpluggedLocked(boolean wasPowered, int oldPlugType) { + if (mWakeUpWhenPluggedOrUnpluggedConfig) { + // FIXME: Need more accurate detection of wireless chargers. + // + // We are unable to accurately detect whether the device is resting on the + // charger unless it is actually receiving power. This causes us some grief + // because the device might not appear to be plugged into the wireless charger + // unless it actually charging. + // + // To avoid spuriously waking the screen, we apply a special policy to + // wireless chargers. + // + // 1. Don't wake the device when unplugged from wireless charger because + // it might be that the device is still resting on the wireless charger + // but is not receiving power anymore because the battery is full. + // + // 2. Don't wake the device when plugged into a wireless charger if the + // battery already appears to be mostly full. This situation may indicate + // that the device was resting on the charger the whole time and simply + // wasn't receiving power because the battery was full. We can't tell + // whether the device was just placed on the charger or whether it has + // been there for half of the night slowly discharging until it hit + // the point where it needed to start charging again. + if (wasPowered && !mIsPowered + && oldPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) { + return false; + } + if (!wasPowered && mIsPowered + && mPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS + && mBatteryService.getBatteryLevel() >= + WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT) { + return false; + } + return true; + } + return false; + } + /** * Updates the value of mStayOn. * Sets DIRTY_STAY_ON if a change occurred. @@ -1113,7 +1174,7 @@ public final class PowerManagerService extends IPowerManager.Stub long nextTimeout = 0; if (mWakefulness != WAKEFULNESS_ASLEEP) { final int screenOffTimeout = getScreenOffTimeoutLocked(); - final int screenDimDuration = getScreenDimDurationLocked(); + final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); mUserActivitySummary = 0; if (mLastUserActivityTime >= mLastWakeTime) { @@ -1187,8 +1248,9 @@ public final class PowerManagerService extends IPowerManager.Stub return Math.max(timeout, MINIMUM_SCREEN_OFF_TIMEOUT); } - private int getScreenDimDurationLocked() { - return SCREEN_DIM_DURATION; + private int getScreenDimDurationLocked(int screenOffTimeout) { + return Math.min(SCREEN_DIM_DURATION, + (int)(screenOffTimeout * MAXIMUM_SCREEN_DIM_RATIO)); } /** @@ -1886,6 +1948,7 @@ public final class PowerManagerService extends IPowerManager.Stub pw.println(" mDirty=0x" + Integer.toHexString(mDirty)); pw.println(" mWakefulness=" + wakefulnessToString(mWakefulness)); pw.println(" mIsPowered=" + mIsPowered); + pw.println(" mPlugType=" + mPlugType); pw.println(" mStayOn=" + mStayOn); pw.println(" mBootCompleted=" + mBootCompleted); pw.println(" mSystemReady=" + mSystemReady); @@ -1931,6 +1994,12 @@ public final class PowerManagerService extends IPowerManager.Stub pw.println(" mScreenBrightnessSettingMaximum=" + mScreenBrightnessSettingMaximum); pw.println(" mScreenBrightnessSettingDefault=" + mScreenBrightnessSettingDefault); + final int screenOffTimeout = getScreenOffTimeoutLocked(); + final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); + pw.println(); + pw.println("Screen off timeout: " + screenOffTimeout + " ms"); + pw.println("Screen dim duration: " + screenDimDuration + " ms"); + pw.println(); pw.println("Wake Locks: size=" + mWakeLocks.size()); for (WakeLock wl : mWakeLocks) { diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index 10011aadf6cb..95797ef2301e 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -16,9 +16,9 @@ package com.android.server.usb; -import android.app.PendingIntent; import android.app.Notification; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -30,23 +30,19 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; -import android.net.Uri; -import android.os.Binder; -import android.os.Bundle; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.os.Parcelable; import android.os.ParcelFileDescriptor; import android.os.Process; -import android.os.UserHandle; -import android.os.storage.StorageManager; -import android.os.storage.StorageVolume; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UEventObserver; +import android.os.UserHandle; +import android.os.storage.StorageManager; +import android.os.storage.StorageVolume; import android.provider.Settings; import android.util.Pair; import android.util.Slog; @@ -56,10 +52,9 @@ import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.HashMap; import java.util.Map; import java.util.Scanner; @@ -106,9 +101,12 @@ public class UsbDeviceManager { private UsbHandler mHandler; private boolean mBootCompleted; + private final Object mLock = new Object(); + private final Context mContext; private final ContentResolver mContentResolver; - private final UsbSettingsManager mSettingsManager; + // @GuardedBy("mLock") + private UsbSettingsManager mCurrentSettings; private NotificationManager mNotificationManager; private final boolean mHasUsbAccessory; private boolean mUseUsbNotification; @@ -149,10 +147,9 @@ public class UsbDeviceManager { } }; - public UsbDeviceManager(Context context, UsbSettingsManager settingsManager) { + public UsbDeviceManager(Context context) { mContext = context; mContentResolver = context.getContentResolver(); - mSettingsManager = settingsManager; PackageManager pm = mContext.getPackageManager(); mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); initRndisAddress(); @@ -175,6 +172,18 @@ public class UsbDeviceManager { } } + public void setCurrentSettings(UsbSettingsManager settings) { + synchronized (mLock) { + mCurrentSettings = settings; + } + } + + private UsbSettingsManager getCurrentSettings() { + synchronized (mLock) { + return mCurrentSettings; + } + } + public void systemReady() { if (DEBUG) Slog.d(TAG, "systemReady"); @@ -516,7 +525,7 @@ public class UsbDeviceManager { Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); // defer accessoryAttached if system is not ready if (mBootCompleted) { - mSettingsManager.accessoryAttached(mCurrentAccessory); + getCurrentSettings().accessoryAttached(mCurrentAccessory); } // else handle in mBootCompletedReceiver } else { Slog.e(TAG, "nativeGetAccessoryStrings failed"); @@ -529,7 +538,7 @@ public class UsbDeviceManager { if (mCurrentAccessory != null) { if (mBootCompleted) { - mSettingsManager.accessoryDetached(mCurrentAccessory); + getCurrentSettings().accessoryDetached(mCurrentAccessory); } mCurrentAccessory = null; mAccessoryStrings = null; @@ -618,7 +627,7 @@ public class UsbDeviceManager { case MSG_BOOT_COMPLETED: mBootCompleted = true; if (mCurrentAccessory != null) { - mSettingsManager.accessoryAttached(mCurrentAccessory); + getCurrentSettings().accessoryAttached(mCurrentAccessory); } if (mDebuggingManager != null) { mDebuggingManager.setAdbEnabled(mAdbEnabled); @@ -774,7 +783,7 @@ public class UsbDeviceManager { + currentAccessory; throw new IllegalArgumentException(error); } - mSettingsManager.checkPermission(accessory); + getCurrentSettings().checkPermission(accessory); return nativeOpenAccessory(); } diff --git a/services/java/com/android/server/usb/UsbHostManager.java b/services/java/com/android/server/usb/UsbHostManager.java index 0a0ff598d7d8..175ae6f0677a 100644 --- a/services/java/com/android/server/usb/UsbHostManager.java +++ b/services/java/com/android/server/usb/UsbHostManager.java @@ -16,35 +16,19 @@ package com.android.server.usb; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; -import android.net.Uri; -import android.os.Binder; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Parcelable; import android.os.ParcelFileDescriptor; -import android.os.UEventObserver; -import android.provider.Settings; +import android.os.Parcelable; import android.util.Slog; -import java.io.File; import java.io.FileDescriptor; -import java.io.FileReader; import java.io.PrintWriter; import java.util.HashMap; -import java.util.List; /** * UsbHostManager manages USB state in host mode. @@ -54,22 +38,35 @@ public class UsbHostManager { private static final boolean LOG = false; // contains all connected USB devices - private final HashMap<String,UsbDevice> mDevices = new HashMap<String,UsbDevice>(); + private final HashMap<String, UsbDevice> mDevices = new HashMap<String, UsbDevice>(); // USB busses to exclude from USB host support private final String[] mHostBlacklist; private final Context mContext; private final Object mLock = new Object(); - private final UsbSettingsManager mSettingsManager; - public UsbHostManager(Context context, UsbSettingsManager settingsManager) { + // @GuardedBy("mLock") + private UsbSettingsManager mCurrentSettings; + + public UsbHostManager(Context context) { mContext = context; - mSettingsManager = settingsManager; mHostBlacklist = context.getResources().getStringArray( com.android.internal.R.array.config_usbHostBlacklist); } + public void setCurrentSettings(UsbSettingsManager settings) { + synchronized (mLock) { + mCurrentSettings = settings; + } + } + + private UsbSettingsManager getCurrentSettings() { + synchronized (mLock) { + return mCurrentSettings; + } + } + private boolean isBlackListed(String deviceName) { int count = mHostBlacklist.length; for (int i = 0; i < count; i++) { @@ -154,7 +151,7 @@ public class UsbHostManager { UsbDevice device = new UsbDevice(deviceName, vendorID, productID, deviceClass, deviceSubclass, deviceProtocol, interfaces); mDevices.put(deviceName, device); - mSettingsManager.deviceAttached(device); + getCurrentSettings().deviceAttached(device); } } @@ -163,7 +160,7 @@ public class UsbHostManager { synchronized (mLock) { UsbDevice device = mDevices.remove(deviceName); if (device != null) { - mSettingsManager.deviceDetached(device); + getCurrentSettings().deviceDetached(device); } } } @@ -202,7 +199,7 @@ public class UsbHostManager { throw new IllegalArgumentException( "device " + deviceName + " does not exist or is restricted"); } - mSettingsManager.checkPermission(device); + getCurrentSettings().checkPermission(device); return nativeOpenDevice(deviceName); } } diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index bebcd5605fb5..629f5fa4bda8 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -17,15 +17,20 @@ package com.android.server.usb; import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; -import android.net.Uri; -import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.UserHandle; +import android.util.SparseArray; + +import com.android.internal.util.IndentingPrintWriter; import java.io.File; import java.io.FileDescriptor; @@ -37,21 +42,72 @@ import java.io.PrintWriter; * support is delegated to UsbDeviceManager. */ public class UsbService extends IUsbManager.Stub { + private static final String TAG = "UsbService"; + private final Context mContext; + private UsbDeviceManager mDeviceManager; private UsbHostManager mHostManager; - private final UsbSettingsManager mSettingsManager; + private final Object mLock = new Object(); + + /** Map from {@link UserHandle} to {@link UsbSettingsManager} */ + // @GuardedBy("mLock") + private final SparseArray<UsbSettingsManager> + mSettingsByUser = new SparseArray<UsbSettingsManager>(); + + private UsbSettingsManager getSettingsForUser(int userId) { + synchronized (mLock) { + UsbSettingsManager settings = mSettingsByUser.get(userId); + if (settings == null) { + settings = new UsbSettingsManager(mContext, new UserHandle(userId)); + mSettingsByUser.put(userId, settings); + } + return settings; + } + } public UsbService(Context context) { mContext = context; - mSettingsManager = new UsbSettingsManager(context); - PackageManager pm = mContext.getPackageManager(); + + final PackageManager pm = mContext.getPackageManager(); if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) { - mHostManager = new UsbHostManager(context, mSettingsManager); + mHostManager = new UsbHostManager(context); } if (new File("/sys/class/android_usb").exists()) { - mDeviceManager = new UsbDeviceManager(context, mSettingsManager); + mDeviceManager = new UsbDeviceManager(context); + } + + setCurrentUser(UserHandle.USER_OWNER); + + final IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_SWITCHED); + userFilter.addAction(Intent.ACTION_USER_STOPPED); + mContext.registerReceiver(mUserReceiver, userFilter, null, null); + } + + private BroadcastReceiver mUserReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + final String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + setCurrentUser(userId); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + synchronized (mLock) { + mSettingsByUser.remove(userId); + } + } + } + }; + + private void setCurrentUser(int userId) { + final UsbSettingsManager userSettings = getSettingsForUser(userId); + if (mHostManager != null) { + mHostManager.setCurrentSettings(userSettings); + } + if (mDeviceManager != null) { + mDeviceManager.setCurrentSettings(userSettings); } } @@ -65,6 +121,7 @@ public class UsbService extends IUsbManager.Stub { } /* Returns a list of all currently attached USB devices (host mdoe) */ + @Override public void getDeviceList(Bundle devices) { if (mHostManager != null) { mHostManager.getDeviceList(devices); @@ -72,6 +129,7 @@ public class UsbService extends IUsbManager.Stub { } /* Opens the specified USB device (host mode) */ + @Override public ParcelFileDescriptor openDevice(String deviceName) { if (mHostManager != null) { return mHostManager.openDevice(deviceName); @@ -81,6 +139,7 @@ public class UsbService extends IUsbManager.Stub { } /* returns the currently attached USB accessory (device mode) */ + @Override public UsbAccessory getCurrentAccessory() { if (mDeviceManager != null) { return mDeviceManager.getCurrentAccessory(); @@ -90,6 +149,7 @@ public class UsbService extends IUsbManager.Stub { } /* opens the currently attached USB accessory (device mode) */ + @Override public ParcelFileDescriptor openAccessory(UsbAccessory accessory) { if (mDeviceManager != null) { return mDeviceManager.openAccessory(accessory); @@ -98,54 +158,70 @@ public class UsbService extends IUsbManager.Stub { } } - public void setDevicePackage(UsbDevice device, String packageName) { + @Override + public void setDevicePackage(UsbDevice device, String packageName, int userId) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mSettingsManager.setDevicePackage(device, packageName); + getSettingsForUser(userId).setDevicePackage(device, packageName); } - public void setAccessoryPackage(UsbAccessory accessory, String packageName) { + @Override + public void setAccessoryPackage(UsbAccessory accessory, String packageName, int userId) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mSettingsManager.setAccessoryPackage(accessory, packageName); + getSettingsForUser(userId).setAccessoryPackage(accessory, packageName); } + @Override public boolean hasDevicePermission(UsbDevice device) { - return mSettingsManager.hasPermission(device); + final int userId = UserHandle.getCallingUserId(); + return getSettingsForUser(userId).hasPermission(device); } + @Override public boolean hasAccessoryPermission(UsbAccessory accessory) { - return mSettingsManager.hasPermission(accessory); + final int userId = UserHandle.getCallingUserId(); + return getSettingsForUser(userId).hasPermission(accessory); } - public void requestDevicePermission(UsbDevice device, String packageName, - PendingIntent pi) { - mSettingsManager.requestPermission(device, packageName, pi); + @Override + public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) { + final int userId = UserHandle.getCallingUserId(); + getSettingsForUser(userId).requestPermission(device, packageName, pi); } - public void requestAccessoryPermission(UsbAccessory accessory, String packageName, - PendingIntent pi) { - mSettingsManager.requestPermission(accessory, packageName, pi); + @Override + public void requestAccessoryPermission( + UsbAccessory accessory, String packageName, PendingIntent pi) { + final int userId = UserHandle.getCallingUserId(); + getSettingsForUser(userId).requestPermission(accessory, packageName, pi); } + @Override public void grantDevicePermission(UsbDevice device, int uid) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mSettingsManager.grantDevicePermission(device, uid); + final int userId = UserHandle.getUserId(uid); + getSettingsForUser(userId).grantDevicePermission(device, uid); } + @Override public void grantAccessoryPermission(UsbAccessory accessory, int uid) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mSettingsManager.grantAccessoryPermission(accessory, uid); + final int userId = UserHandle.getUserId(uid); + getSettingsForUser(userId).grantAccessoryPermission(accessory, uid); } - public boolean hasDefaults(String packageName) { + @Override + public boolean hasDefaults(String packageName, int userId) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - return mSettingsManager.hasDefaults(packageName); + return getSettingsForUser(userId).hasDefaults(packageName); } - public void clearDefaults(String packageName) { + @Override + public void clearDefaults(String packageName, int userId) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mSettingsManager.clearDefaults(packageName); + getSettingsForUser(userId).clearDefaults(packageName); } + @Override public void setCurrentFunction(String function, boolean makeDefault) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); if (mDeviceManager != null) { @@ -155,6 +231,7 @@ public class UsbService extends IUsbManager.Stub { } } + @Override public void setMassStorageBackingFile(String path) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); if (mDeviceManager != null) { @@ -164,34 +241,41 @@ public class UsbService extends IUsbManager.Stub { } } + @Override public void allowUsbDebugging(boolean alwaysAllow, String publicKey) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); mDeviceManager.allowUsbDebugging(alwaysAllow, publicKey); } + @Override public void denyUsbDebugging() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); mDeviceManager.denyUsbDebugging(); } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump UsbManager from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); pw.println("USB Manager State:"); - if (mDeviceManager != null) { mDeviceManager.dump(fd, pw); } if (mHostManager != null) { mHostManager.dump(fd, pw); } - mSettingsManager.dump(fd, pw); + + synchronized (mLock) { + for (int i = 0; i < mSettingsByUser.size(); i++) { + final int userId = mSettingsByUser.keyAt(i); + final UsbSettingsManager settings = mSettingsByUser.valueAt(i); + pw.increaseIndent(); + pw.println("Settings for user " + userId + ":"); + settings.dump(fd, pw); + pw.decreaseIndent(); + } + } + pw.decreaseIndent(); } } diff --git a/services/java/com/android/server/usb/UsbSettingsManager.java b/services/java/com/android/server/usb/UsbSettingsManager.java index a8453d3d78b1..4b2bbfe0006b 100644 --- a/services/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/java/com/android/server/usb/UsbSettingsManager.java @@ -33,9 +33,10 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.os.Binder; -import android.os.FileUtils; -import android.os.Process; +import android.os.Environment; import android.os.UserHandle; +import android.util.AtomicFile; +import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.Xml; @@ -48,7 +49,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -60,13 +60,21 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -class UsbSettingsManager { +import libcore.io.IoUtils; +class UsbSettingsManager { private static final String TAG = "UsbSettingsManager"; private static final boolean DEBUG = false; - private static final File sSettingsFile = new File("/data/system/usb_device_manager.xml"); + + /** Legacy settings file, before multi-user */ + private static final File sSingleUserSettingsFile = new File( + "/data/system/usb_device_manager.xml"); + + private final UserHandle mUser; + private final AtomicFile mSettingsFile; private final Context mContext; + private final Context mUserContext; private final PackageManager mPackageManager; // Temporary mapping USB device name to list of UIDs with permissions for the device @@ -350,28 +358,49 @@ class UsbSettingsManager { } private class MyPackageMonitor extends PackageMonitor { - + @Override public void onPackageAdded(String packageName, int uid) { handlePackageUpdate(packageName); } + @Override public void onPackageChanged(String packageName, int uid, String[] components) { handlePackageUpdate(packageName); } + @Override public void onPackageRemoved(String packageName, int uid) { clearDefaults(packageName); } } + MyPackageMonitor mPackageMonitor = new MyPackageMonitor(); - public UsbSettingsManager(Context context) { + public UsbSettingsManager(Context context, UserHandle user) { + if (DEBUG) Slog.v(TAG, "Creating settings for " + user); + + try { + mUserContext = context.createPackageContextAsUser("android", 0, user); + } catch (NameNotFoundException e) { + throw new RuntimeException("Missing android package"); + } + mContext = context; - mPackageManager = context.getPackageManager(); + mPackageManager = mUserContext.getPackageManager(); + + mUser = user; + mSettingsFile = new AtomicFile(new File( + Environment.getUserSystemDirectory(user.getIdentifier()), + "usb_device_manager.xml")); + synchronized (mLock) { + if (UserHandle.OWNER.equals(user)) { + upgradeSingleUserLocked(); + } readSettingsLocked(); } - mPackageMonitor.register(context, null, true); + + mPackageMonitor.register(mUserContext, null, true); } private void readPreference(XmlPullParser parser) @@ -395,10 +424,54 @@ class UsbSettingsManager { XmlUtils.nextElement(parser); } + /** + * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}. + * Should only by called by owner. + */ + private void upgradeSingleUserLocked() { + if (sSingleUserSettingsFile.exists()) { + mDevicePreferenceMap.clear(); + mAccessoryPreferenceMap.clear(); + + FileInputStream fis = null; + try { + fis = new FileInputStream(sSingleUserSettingsFile); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + final String tagName = parser.getName(); + if ("preference".equals(tagName)) { + readPreference(parser); + } else { + XmlUtils.nextElement(parser); + } + } + } catch (IOException e) { + Log.wtf(TAG, "Failed to read single-user settings", e); + } catch (XmlPullParserException e) { + Log.wtf(TAG, "Failed to read single-user settings", e); + } finally { + IoUtils.closeQuietly(fis); + } + + writeSettingsLocked(); + + // Success or failure, we delete single-user file + sSingleUserSettingsFile.delete(); + } + } + private void readSettingsLocked() { + if (DEBUG) Slog.v(TAG, "readSettingsLocked()"); + + mDevicePreferenceMap.clear(); + mAccessoryPreferenceMap.clear(); + FileInputStream stream = null; try { - stream = new FileInputStream(sSettingsFile); + stream = mSettingsFile.openRead(); XmlPullParser parser = Xml.newPullParser(); parser.setInput(stream, null); @@ -407,7 +480,7 @@ class UsbSettingsManager { String tagName = parser.getName(); if ("preference".equals(tagName)) { readPreference(parser); - } else { + } else { XmlUtils.nextElement(parser); } } @@ -415,25 +488,21 @@ class UsbSettingsManager { if (DEBUG) Slog.d(TAG, "settings file not found"); } catch (Exception e) { Slog.e(TAG, "error reading settings file, deleting to start fresh", e); - sSettingsFile.delete(); + mSettingsFile.delete(); } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - } - } + IoUtils.closeQuietly(stream); } } private void writeSettingsLocked() { + if (DEBUG) Slog.v(TAG, "writeSettingsLocked()"); + FileOutputStream fos = null; try { - FileOutputStream fstr = new FileOutputStream(sSettingsFile); - if (DEBUG) Slog.d(TAG, "writing settings to " + fstr); - BufferedOutputStream str = new BufferedOutputStream(fstr); + fos = mSettingsFile.startWrite(); + FastXmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(str, "utf-8"); + serializer.setOutput(fos, "utf-8"); serializer.startDocument(null, true); serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, "settings"); @@ -455,12 +524,12 @@ class UsbSettingsManager { serializer.endTag(null, "settings"); serializer.endDocument(); - str.flush(); - FileUtils.sync(fstr); - str.close(); - } catch (Exception e) { - Slog.e(TAG, "error writing settings file, deleting to start fresh", e); - sSettingsFile.delete(); + mSettingsFile.finishWrite(fos); + } catch (IOException e) { + Slog.e(TAG, "Failed to write settings", e); + if (fos != null) { + mSettingsFile.failWrite(fos); + } } } @@ -547,7 +616,7 @@ class UsbSettingsManager { } // Send broadcast to running activity with registered intent - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + mUserContext.sendBroadcast(intent); // Start activity with registered intent resolveActivity(intent, matches, defaultPackage, device, null); @@ -608,7 +677,7 @@ class UsbSettingsManager { dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); dialogIntent.putExtra("uri", uri); try { - mContext.startActivity(dialogIntent); + mUserContext.startActivityAsUser(dialogIntent, mUser); } catch (ActivityNotFoundException e) { Slog.e(TAG, "unable to start UsbAccessoryUriActivity"); } @@ -656,7 +725,7 @@ class UsbSettingsManager { intent.setComponent( new ComponentName(defaultRI.activityInfo.packageName, defaultRI.activityInfo.name)); - mContext.startActivity(intent); + mUserContext.startActivityAsUser(intent, mUser); } catch (ActivityNotFoundException e) { Slog.e(TAG, "startActivity failed", e); } @@ -683,7 +752,7 @@ class UsbSettingsManager { resolverIntent.putExtra(Intent.EXTRA_INTENT, intent); } try { - mContext.startActivity(resolverIntent); + mUserContext.startActivityAsUser(resolverIntent, mUser); } catch (ActivityNotFoundException e) { Slog.e(TAG, "unable to start activity " + resolverIntent); } @@ -814,7 +883,7 @@ class UsbSettingsManager { } private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) { - int uid = Binder.getCallingUid(); + final int uid = Binder.getCallingUid(); // compare uid with packageName to foil apps pretending to be someone else try { @@ -833,9 +902,9 @@ class UsbSettingsManager { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(Intent.EXTRA_INTENT, pi); intent.putExtra("package", packageName); - intent.putExtra("uid", uid); + intent.putExtra(Intent.EXTRA_UID, uid); try { - mContext.startActivity(intent); + mUserContext.startActivityAsUser(intent, mUser); } catch (ActivityNotFoundException e) { Slog.e(TAG, "unable to start UsbPermissionActivity"); } finally { @@ -851,7 +920,7 @@ class UsbSettingsManager { intent.putExtra(UsbManager.EXTRA_DEVICE, device); intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); try { - pi.send(mContext, 0, intent); + pi.send(mUserContext, 0, intent); } catch (PendingIntent.CanceledException e) { if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); } @@ -864,14 +933,14 @@ class UsbSettingsManager { } public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) { - Intent intent = new Intent(); + Intent intent = new Intent(); // respond immediately if permission has already been granted if (hasPermission(accessory)) { intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); - try { - pi.send(mContext, 0, intent); + try { + pi.send(mUserContext, 0, intent); } catch (PendingIntent.CanceledException e) { if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled"); } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 8fb1459cd7c9..180579dc025c 100755 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -3639,8 +3639,6 @@ public class WindowManagerService extends IWindowManager.Stub if (wtoken != null) { boolean delayed = false; if (!wtoken.hidden) { - wtoken.hidden = true; - final int N = wtoken.windows.size(); boolean changed = false; @@ -3661,6 +3659,8 @@ public class WindowManagerService extends IWindowManager.Stub } } + wtoken.hidden = true; + if (changed) { performLayoutAndPlaceSurfacesLocked(); updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, |