diff options
55 files changed, 1414 insertions, 969 deletions
diff --git a/api/current.txt b/api/current.txt index de8cab1fe317..c62d82b2b4c6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -14787,6 +14787,7 @@ package android.os { field public static final int FLAG_ONEWAY = 1; // 0x1 field public static final int INTERFACE_TRANSACTION = 1598968902; // 0x5f4e5446 field public static final int LAST_CALL_TRANSACTION = 16777215; // 0xffffff + field public static final int LIKE_TRANSACTION = 1598835019; // 0x5f4c494b field public static final int PING_TRANSACTION = 1599098439; // 0x5f504e47 field public static final int TWEET_TRANSACTION = 1599362900; // 0x5f545754 } diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index 26b911356699..4ede33f73daf 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -184,6 +184,7 @@ int free_cache(int64_t free_size) DIR *d; struct dirent *de; int64_t avail; + char datadir[PKG_PATH_MAX]; avail = disk_free(); if (avail < 0) return -1; @@ -191,9 +192,14 @@ int free_cache(int64_t free_size) LOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", free_size, avail); if (avail >= free_size) return 0; - d = opendir(android_data_dir.path); + if (create_persona_path(datadir, 0)) { + LOGE("couldn't get directory for persona 0"); + return -1; + } + + d = opendir(datadir); if (d == NULL) { - LOGE("cannot open %s: %s\n", android_data_dir.path, strerror(errno)); + LOGE("cannot open %s: %s\n", datadir, strerror(errno)); return -1; } dfd = dirfd(d); @@ -578,19 +584,6 @@ fail: return -1; } -int create_move_path(char path[PKG_PATH_MAX], - const char* pkgname, - const char* leaf, - uid_t persona) -{ - if ((android_data_dir.len + strlen(pkgname) + strlen(leaf) + 1) >= PKG_PATH_MAX) { - return -1; - } - - sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf); - return 0; -} - void mkinnerdirs(char* path, int basepos, mode_t mode, int uid, int gid, struct stat* statbuf) { diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index c5872b8efa09..173cabfb77a7 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -105,6 +105,11 @@ int create_pkg_path(char path[PKG_PATH_MAX], int create_persona_path(char path[PKG_PATH_MAX], uid_t persona); +int create_move_path(char path[PKG_PATH_MAX], + const char* pkgname, + const char* leaf, + uid_t persona); + int is_valid_package_name(const char* pkgname); int create_cache_path(char path[PKG_PATH_MAX], const char *src); diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp index 1128fceca0af..7cb9b37fe038 100644 --- a/cmds/installd/tests/installd_utils_test.cpp +++ b/cmds/installd/tests/installd_utils_test.cpp @@ -34,6 +34,16 @@ extern "C" { #define TEST_SYSTEM_DIR1 "/system/app/" #define TEST_SYSTEM_DIR2 "/vendor/app/" +#define REALLY_LONG_APP_NAME "com.example." \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa." \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +#define REALLY_LONG_LEAF_NAME "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \ + "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \ + "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" \ + "shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_shared_prefs_" + namespace android { class UtilsTest : public testing::Test { @@ -210,7 +220,7 @@ TEST_F(UtilsTest, CheckSystemApp_BadPathEscapeFail) { TEST_F(UtilsTest, GetPathFromString_NullPathFail) { dir_rec_t test1; - EXPECT_EQ(-1, get_path_from_string(&test1, NULL)) + EXPECT_EQ(-1, get_path_from_string(&test1, (const char *) NULL)) << "Should not allow NULL as a path."; } @@ -327,6 +337,50 @@ TEST_F(UtilsTest, CreatePkgPathInDir_ProtectedDir) { << "Package path should be in /data/app-private/"; } +TEST_F(UtilsTest, CreatePersonaPath_Primary) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_persona_path(path, 0)) + << "Should successfully build primary user path."; + + EXPECT_STREQ("/data/data/", path) + << "Primary user should have correct path"; +} + +TEST_F(UtilsTest, CreatePersonaPath_Secondary) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_persona_path(path, 1)) + << "Should successfully build primary user path."; + + EXPECT_STREQ("/data/user/1/", path) + << "Primary user should have correct path"; +} + +TEST_F(UtilsTest, CreateMovePath_Primary) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(0, create_move_path(path, "com.android.test", "shared_prefs", 0)) + << "Should be able to create move path for primary user"; + + EXPECT_STREQ("/data/data/com.android.test/shared_prefs", path) + << "Primary user package directory should be created correctly"; +} + +TEST_F(UtilsTest, CreateMovePath_Fail_AppTooLong) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(-1, create_move_path(path, REALLY_LONG_APP_NAME, "shared_prefs", 0)) + << "Should fail to create move path for primary user"; +} + +TEST_F(UtilsTest, CreateMovePath_Fail_LeafTooLong) { + char path[PKG_PATH_MAX]; + + EXPECT_EQ(-1, create_move_path(path, "com.android.test", REALLY_LONG_LEAF_NAME, 0)) + << "Should fail to create move path for primary user"; +} + TEST_F(UtilsTest, CopyAndAppend_Normal) { //int copy_and_append(dir_rec_t* dst, dir_rec_t* src, char* suffix) dir_rec_t dst; diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c index 3099b8341f6e..a53a93ce1073 100644 --- a/cmds/installd/utils.c +++ b/cmds/installd/utils.c @@ -109,7 +109,7 @@ int create_persona_path(char path[PKG_PATH_MAX], uid_len = 0; } else { persona_prefix = SECONDARY_USER_PREFIX; - uid_len = snprintf(NULL, 0, "%d", persona); + uid_len = snprintf(NULL, 0, "%d/", persona); } char *dst = path; @@ -126,7 +126,7 @@ int create_persona_path(char path[PKG_PATH_MAX], LOGE("Error building user path"); return -1; } - int ret = snprintf(dst, dst_size, "%d", persona); + int ret = snprintf(dst, dst_size, "%d/", persona); if (ret < 0 || (size_t) ret != uid_len) { LOGE("Error appending persona id to path"); return -1; @@ -135,6 +135,20 @@ int create_persona_path(char path[PKG_PATH_MAX], return 0; } +int create_move_path(char path[PKG_PATH_MAX], + const char* pkgname, + const char* leaf, + uid_t persona) +{ + if ((android_data_dir.len + strlen(PRIMARY_USER_PREFIX) + strlen(pkgname) + strlen(leaf) + 1) + >= PKG_PATH_MAX) { + return -1; + } + + sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf); + return 0; +} + /** * Checks whether the package name is valid. Returns -1 on error and * 0 on success. diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 64a275553339..211be52e0622 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -16,8 +16,6 @@ package android.accessibilityservice; -import com.android.internal.os.HandlerCaller; - import android.app.Service; import android.content.Intent; import android.os.IBinder; @@ -25,8 +23,11 @@ import android.os.Message; import android.os.RemoteException; import android.util.Log; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityNodeInfo; +import com.android.internal.os.HandlerCaller; + /** * An accessibility service runs in the background and receives callbacks by the system * when {@link AccessibilityEvent}s are fired. Such events denote some state transition @@ -219,7 +220,7 @@ public abstract class AccessibilityService extends Service { private AccessibilityServiceInfo mInfo; - IAccessibilityServiceConnection mConnection; + private int mConnectionId; /** * Callback for {@link android.view.accessibility.AccessibilityEvent}s. @@ -264,9 +265,11 @@ public abstract class AccessibilityService extends Service { * AccessibilityManagerService. */ private void sendServiceInfo() { - if (mInfo != null && mConnection != null) { + IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (mInfo != null && connection != null) { try { - mConnection.setServiceInfo(mInfo); + connection.setServiceInfo(mInfo); } catch (RemoteException re) { Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re); } @@ -302,8 +305,9 @@ public abstract class AccessibilityService extends Service { mCaller = new HandlerCaller(context, this); } - public void setConnection(IAccessibilityServiceConnection connection) { - Message message = mCaller.obtainMessageO(DO_SET_SET_CONNECTION, connection); + public void setConnection(IAccessibilityServiceConnection connection, int connectionId) { + Message message = mCaller.obtainMessageIO(DO_SET_SET_CONNECTION, connectionId, + connection); mCaller.sendMessage(message); } @@ -330,8 +334,19 @@ public abstract class AccessibilityService extends Service { mTarget.onInterrupt(); return; case DO_SET_SET_CONNECTION : - mConnection = ((IAccessibilityServiceConnection) message.obj); - mTarget.onServiceConnected(); + final int connectionId = message.arg1; + IAccessibilityServiceConnection connection = + (IAccessibilityServiceConnection) message.obj; + if (connection != null) { + AccessibilityInteractionClient.getInstance().addConnection(connectionId, + connection); + mConnectionId = connectionId; + mTarget.onServiceConnected(); + } else { + AccessibilityInteractionClient.getInstance().removeConnection(connectionId); + mConnectionId = AccessibilityInteractionClient.NO_ID; + // TODO: Do we need a onServiceDisconnected callback? + } return; default : Log.w(LOG_TAG, "Unknown message type " + message.what); diff --git a/core/java/android/accessibilityservice/IEventListener.aidl b/core/java/android/accessibilityservice/IEventListener.aidl index 5b849f1e40fd..5536b3c05446 100644 --- a/core/java/android/accessibilityservice/IEventListener.aidl +++ b/core/java/android/accessibilityservice/IEventListener.aidl @@ -26,7 +26,7 @@ import android.view.accessibility.AccessibilityEvent; */ oneway interface IEventListener { - void setConnection(in IAccessibilityServiceConnection connection); + void setConnection(in IAccessibilityServiceConnection connection, int connectionId); void onAccessibilityEvent(in AccessibilityEvent event); diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 81defd690af8..0586d9ed1eeb 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -128,6 +128,19 @@ public interface IBinder { int TWEET_TRANSACTION = ('_'<<24)|('T'<<16)|('W'<<8)|'T'; /** + * IBinder protocol transaction code: tell an app asynchronously that the + * caller likes it. The app is responsible for incrementing and maintaining + * its own like counter, and may display this value to the user to indicate the + * quality of the app. This is an optional command that applications do not + * need to handle, so the default implementation is to do nothing. + * + * <p>There is no response returned and nothing about the + * system will be functionally affected by it, but it will improve the + * app's self-esteem. + */ + int LIKE_TRANSACTION = ('_'<<24)|('L'<<16)|('I'<<8)|'K'; + + /** * Flag to {@link #transact}: this is a one-way call, meaning that the * caller returns immediately, without waiting for a result from the * callee. Applies only if the caller and callee are in different diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ee3215c6c34f..15e44380a05f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4019,28 +4019,6 @@ public final class Settings { public static final String SETUP_PREPAID_DETECTION_REDIR_HOST = "setup_prepaid_detection_redir_host"; - /** - * Whether the screensaver is enabled. - * @hide - */ - public static final String SCREENSAVER_ENABLED = "screensaver_enabled"; - - /** - * The user's chosen screensaver component. - * - * This component will be launched by the PhoneWindowManager after a timeout when not on - * battery, or upon dock insertion (if SCREENSAVER_ACTIVATE_ON_DOCK is set to 1). - * @hide - */ - public static final String SCREENSAVER_COMPONENT = "screensaver_component"; - - /** - * Whether the screensaver should be automatically launched when the device is inserted - * into a (desk) dock. - * @hide - */ - public static final String SCREENSAVER_ACTIVATE_ON_DOCK = "screensaver_activate_on_dock"; - /** {@hide} */ public static final String NETSTATS_ENABLED = "netstats_enabled"; /** {@hide} */ diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index a7d8cace28e5..94fbbc8d33e2 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -145,7 +145,12 @@ public class BluetoothService extends IBluetooth.Stub { private final ArrayList<String> mUuidIntentTracker; private final HashMap<RemoteService, IBluetoothCallback> mUuidCallbackTracker; - private final HashMap<Integer, Pair<Integer, IBinder>> mServiceRecordToPid; + private static class ServiceRecordClient { + int pid; + IBinder binder; + IBinder.DeathRecipient death; + } + private final HashMap<Integer, ServiceRecordClient> mServiceRecordToPid; private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState; private final BluetoothProfileState mA2dpProfileState; @@ -221,7 +226,7 @@ public class BluetoothService extends IBluetooth.Stub { mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>(); mUuidIntentTracker = new ArrayList<String>(); mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>(); - mServiceRecordToPid = new HashMap<Integer, Pair<Integer, IBinder>>(); + mServiceRecordToPid = new HashMap<Integer, ServiceRecordClient>(); mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>(); mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP); mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP); @@ -1528,11 +1533,17 @@ public class BluetoothService extends IBluetooth.Stub { return -1; } - int pid = Binder.getCallingPid(); - mServiceRecordToPid.put(new Integer(handle), new Pair<Integer, IBinder>(pid, b)); + ServiceRecordClient client = new ServiceRecordClient(); + client.pid = Binder.getCallingPid(); + client.binder = b; + client.death = new Reaper(handle, client.pid, RFCOMM_RECORD_REAPER); + mServiceRecordToPid.put(new Integer(handle), client); try { - b.linkToDeath(new Reaper(handle, pid, RFCOMM_RECORD_REAPER), 0); - } catch (RemoteException e) {Log.e(TAG, "", e);} + b.linkToDeath(client.death, 0); + } catch (RemoteException e) { + Log.e(TAG, "", e); + client.death = null; + } return handle; } @@ -1547,10 +1558,15 @@ public class BluetoothService extends IBluetooth.Stub { } private synchronized void checkAndRemoveRecord(int handle, int pid) { - Pair<Integer, IBinder> pidPair = mServiceRecordToPid.get(handle); - if (pidPair != null && pid == pidPair.first) { + ServiceRecordClient client = mServiceRecordToPid.get(handle); + if (client != null && pid == client.pid) { if (DBG) Log.d(TAG, "Removing service record " + Integer.toHexString(handle) + " for pid " + pid); + + if (client.death != null) { + client.binder.unlinkToDeath(client.death, 0); + } + mServiceRecordToPid.remove(handle); removeServiceRecordNative(handle); } @@ -1880,7 +1896,7 @@ public class BluetoothService extends IBluetooth.Stub { private void dumpApplicationServiceRecords(PrintWriter pw) { pw.println("\n--Application Service Records--"); for (Integer handle : mServiceRecordToPid.keySet()) { - Integer pid = mServiceRecordToPid.get(handle).first; + Integer pid = mServiceRecordToPid.get(handle).pid; pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle)); } } diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 68fea1907b87..b73d90046303 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -119,6 +119,7 @@ class TextLine { * @param hasTabs true if the line might contain tabs or emoji * @param tabStops the tabStops. Can be null. */ + @SuppressWarnings("null") void set(TextPaint paint, CharSequence text, int start, int limit, int dir, Directions directions, boolean hasTabs, TabStops tabStops) { mPaint = paint; @@ -134,11 +135,12 @@ class TextLine { mSpanned = null; boolean hasReplacement = false; + SpanSet<ReplacementSpan> replacementSpans = null; if (text instanceof Spanned) { mSpanned = (Spanned) text; - ReplacementSpan[] spans = mSpanned.getSpans(start, limit, ReplacementSpan.class); - spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class); - hasReplacement = spans.length > 0; + replacementSpans = new SpanSet<ReplacementSpan>(mSpanned, start, limit, + ReplacementSpan.class); + hasReplacement = replacementSpans.numberOfSpans > 0; } mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT; @@ -156,10 +158,9 @@ class TextLine { // zero-width characters. char[] chars = mChars; for (int i = start, inext; i < limit; i = inext) { - inext = mSpanned.nextSpanTransition(i, limit, ReplacementSpan.class); - ReplacementSpan[] spans = mSpanned.getSpans(i, inext, ReplacementSpan.class); - spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class); - if (spans.length > 0) { + // replacementSpans cannot be null if hasReplacement is true + inext = replacementSpans.getNextTransition(i, limit); + if (replacementSpans.hasSpansIntersecting(i, inext)) { // transition into a span chars[i - start] = '\ufffc'; for (int j = i - start + 1, e = inext - start; j < e; ++j) { @@ -908,6 +909,15 @@ class TextLine { numberOfSpans = count; } + public boolean hasSpansIntersecting(int start, int end) { + for (int i = 0; i < numberOfSpans; i++) { + // equal test is valid since both intervals are not empty by construction + if (spanStarts[i] >= end || spanEnds[i] <= start) continue; + return true; + } + return false; + } + int getNextTransition(int start, int limit) { for (int i = 0; i < numberOfSpans; i++) { final int spanStart = spanStarts[i]; diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java index 106a80190b36..3dfd44d90e4d 100644 --- a/core/java/android/text/method/Touch.java +++ b/core/java/android/text/method/Touch.java @@ -35,22 +35,30 @@ public class Touch { * Y position. */ public static void scrollTo(TextView widget, Layout layout, int x, int y) { - final int verticalPadding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); - final int top = layout.getLineForVertical(y); - final int bottom = layout.getLineForVertical(y + widget.getHeight() - verticalPadding); + final int horizontalPadding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight(); + final int availableWidth = widget.getWidth() - horizontalPadding; - int left = Integer.MAX_VALUE; - int right = 0; + final int top = layout.getLineForVertical(y); Alignment a = layout.getParagraphAlignment(top); boolean ltr = layout.getParagraphDirection(top) > 0; - for (int i = top; i <= bottom; i++) { - left = (int) Math.min(left, layout.getLineLeft(i)); - right = (int) Math.max(right, layout.getLineRight(i)); + int left, right; + if (widget.getHorizontallyScrolling()) { + final int verticalPadding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); + final int bottom = layout.getLineForVertical(y + widget.getHeight() - verticalPadding); + + left = Integer.MAX_VALUE; + right = 0; + + for (int i = top; i <= bottom; i++) { + left = (int) Math.min(left, layout.getLineLeft(i)); + right = (int) Math.max(right, layout.getLineRight(i)); + } + } else { + left = 0; + right = availableWidth; } - final int hoizontalPadding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight(); - final int availableWidth = widget.getWidth() - hoizontalPadding; final int actualWidth = right - left; if (actualWidth < availableWidth) { @@ -166,16 +174,24 @@ public class Touch { return false; } + /** + * @param widget The text view. + * @param buffer The text buffer. + */ public static int getInitialScrollX(TextView widget, Spannable buffer) { DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class); return ds.length > 0 ? ds[0].mScrollX : -1; } - + + /** + * @param widget The text view. + * @param buffer The text buffer. + */ public static int getInitialScrollY(TextView widget, Spannable buffer) { DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class); return ds.length > 0 ? ds[0].mScrollY : -1; } - + private static class DragState implements NoCopySpan { public float mX; public float mY; diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java index 7cf45793fe7e..366abd36294d 100644 --- a/core/java/android/util/SparseArray.java +++ b/core/java/android/util/SparseArray.java @@ -134,6 +134,7 @@ public class SparseArray<E> implements Cloneable { if (i != o) { keys[o] = keys[i]; values[o] = val; + values[i] = null; } o++; diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java index b657204d02fa..48fe0df89577 100644 --- a/core/java/android/view/VolumePanel.java +++ b/core/java/android/view/VolumePanel.java @@ -278,10 +278,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie } private void createSliders() { - final int silentableStreams = System.getInt(mContext.getContentResolver(), - System.MODE_RINGER_STREAMS_AFFECTED, - ((1 << AudioSystem.STREAM_NOTIFICATION) | (1 << AudioSystem.STREAM_RING))); - LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length); @@ -297,9 +293,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null); sc.group.setTag(sc); sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon); - if ((silentableStreams & (1 << sc.streamType)) != 0) { - sc.icon.setOnClickListener(this); - } sc.icon.setTag(sc); sc.icon.setContentDescription(res.getString(streamRes.descRes)); sc.iconRes = streamRes.iconRes; @@ -356,7 +349,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie && mAudioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) { sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate); } - sc.seekbarView.setEnabled(!muted); } private boolean isExpanded() { @@ -436,8 +428,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie mAudioService.getLastAudibleStreamVolume(streamType) : mAudioService.getStreamVolume(streamType); -// int message = UNKNOWN_VOLUME_TEXT; -// int additionalMessage = 0; mRingIsSilent = false; if (LOGD) { @@ -697,18 +687,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie public void onClick(View v) { if (v == mMoreButton) { expand(); - } else if (v.getTag() instanceof StreamControl) { - StreamControl sc = (StreamControl) v.getTag(); - boolean vibeInSilent = Settings.System.getInt(mContext.getContentResolver(), - System.VIBRATE_IN_SILENT, 1) == 1; - int newMode = mAudioManager.isSilentMode() - ? AudioManager.RINGER_MODE_NORMAL - : (vibeInSilent - ? AudioManager.RINGER_MODE_VIBRATE - : AudioManager.RINGER_MODE_SILENT); - mAudioManager.setRingerMode(newMode); - // Expand the dialog if it hasn't been expanded yet. - if (mShowCombinedVolumes && !isExpanded()) expand(); } resetTimeout(); } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 7bf0c833b347..91dcac8db548 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -16,7 +16,6 @@ package android.view.accessibility; -import android.accessibilityservice.IAccessibilityServiceConnection; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -590,24 +589,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** - * Sets the connection for interacting with the AccessibilityManagerService. - * - * @param connection The connection. - * - * @hide - */ - @Override - public void setConnection(IAccessibilityServiceConnection connection) { - super.setConnection(connection); - List<AccessibilityRecord> records = mRecords; - final int recordCount = records.size(); - for (int i = 0; i < recordCount; i++) { - AccessibilityRecord record = records.get(i); - record.setConnection(connection); - } - } - - /** * Sets if this instance is sealed. * * @param sealed Whether is sealed. @@ -821,23 +802,19 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @param parcel A parcel containing the state of a {@link AccessibilityEvent}. */ public void initFromParcel(Parcel parcel) { - if (parcel.readInt() == 1) { - mConnection = IAccessibilityServiceConnection.Stub.asInterface( - parcel.readStrongBinder()); - } - setSealed(parcel.readInt() == 1); + mSealed = (parcel.readInt() == 1); mEventType = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); + mConnectionId = parcel.readInt(); readAccessibilityRecordFromParcel(this, parcel); // Read the records. final int recordCount = parcel.readInt(); for (int i = 0; i < recordCount; i++) { AccessibilityRecord record = AccessibilityRecord.obtain(); - // Do this to write the connection only once. - record.setConnection(mConnection); readAccessibilityRecordFromParcel(record, parcel); + record.mConnectionId = mConnectionId; mRecords.add(record); } } @@ -875,16 +852,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * {@inheritDoc} */ public void writeToParcel(Parcel parcel, int flags) { - if (mConnection == null) { - parcel.writeInt(0); - } else { - parcel.writeInt(1); - parcel.writeStrongBinder(mConnection.asBinder()); - } parcel.writeInt(isSealed() ? 1 : 0); parcel.writeInt(mEventType); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); + parcel.writeInt(mConnectionId); writeAccessibilityRecordToParcel(this, parcel, flags); // Write the records. diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 25b980bc966e..96653e5dd632 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -21,6 +21,8 @@ import android.graphics.Rect; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; import java.util.Collections; import java.util.List; @@ -61,6 +63,12 @@ import java.util.concurrent.atomic.AtomicInteger; public final class AccessibilityInteractionClient extends IAccessibilityInteractionConnectionCallback.Stub { + public static final int NO_ID = -1; + + private static final String LOG_TAG = "AccessibilityInteractionClient"; + + private static final boolean DEBUG = false; + private static final long TIMEOUT_INTERACTION_MILLIS = 5000; private static final Object sStaticLock = new Object(); @@ -83,6 +91,10 @@ public final class AccessibilityInteractionClient private final Rect mTempBounds = new Rect(); + // The connection cache is shared between all interrogating threads. + private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = + new SparseArray<IAccessibilityServiceConnection>(); + /** * @return The singleton of this class. */ @@ -111,28 +123,37 @@ public final class AccessibilityInteractionClient /** * Finds an {@link AccessibilityNodeInfo} by accessibility id. * - * @param connection A connection for interacting with the system. + * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. * @param accessibilityViewId A unique View accessibility id. * @return An {@link AccessibilityNodeInfo} if found, null otherwise. */ - public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId( - IAccessibilityServiceConnection connection, int accessibilityWindowId, - int accessibilityViewId) { + public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId, + int accessibilityWindowId, int accessibilityViewId) { try { - final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId( - accessibilityWindowId, accessibilityViewId, interactionId, this, - Thread.currentThread().getId()); - // If the scale is zero the call has failed. - if (windowScale > 0) { - AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( - interactionId); - finalizeAccessibilityNodeInfo(info, connection, windowScale); - return info; + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + final int interactionId = mInteractionIdCounter.getAndIncrement(); + final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId( + accessibilityWindowId, accessibilityViewId, interactionId, this, + Thread.currentThread().getId()); + // If the scale is zero the call has failed. + if (windowScale > 0) { + AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( + interactionId); + finalizeAccessibilityNodeInfo(info, connectionId, windowScale); + return info; + } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } } } catch (RemoteException re) { - /* ignore */ + if (DEBUG) { + Log.w(LOG_TAG, "Error while calling remote" + + " findAccessibilityNodeInfoByAccessibilityId", re); + } } return null; } @@ -141,25 +162,36 @@ public final class AccessibilityInteractionClient * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed * in the currently active window and starts from the root View in the window. * - * @param connection A connection for interacting with the system. + * @param connectionId The id of a connection for interacting with the system. * @param viewId The id of the view. * @return An {@link AccessibilityNodeInfo} if found, null otherwise. */ - public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow( - IAccessibilityServiceConnection connection, int viewId) { + public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int connectionId, + int viewId) { try { - final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = connection.findAccessibilityNodeInfoByViewIdInActiveWindow( - viewId, interactionId, this, Thread.currentThread().getId()); - // If the scale is zero the call has failed. - if (windowScale > 0) { - AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( - interactionId); - finalizeAccessibilityNodeInfo(info, connection, windowScale); - return info; + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + final int interactionId = mInteractionIdCounter.getAndIncrement(); + final float windowScale = + connection.findAccessibilityNodeInfoByViewIdInActiveWindow(viewId, + interactionId, this, Thread.currentThread().getId()); + // If the scale is zero the call has failed. + if (windowScale > 0) { + AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( + interactionId); + finalizeAccessibilityNodeInfo(info, connectionId, windowScale); + return info; + } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } } } catch (RemoteException re) { - /* ignore */ + if (DEBUG) { + Log.w(LOG_TAG, "Error while calling remote" + + " findAccessibilityNodeInfoByViewIdInActiveWindow", re); + } } return null; } @@ -169,25 +201,36 @@ public final class AccessibilityInteractionClient * insensitive containment. The search is performed in the currently * active window and starts from the root View in the window. * - * @param connection A connection for interacting with the system. + * @param connectionId The id of a connection for interacting with the system. * @param text The searched text. * @return A list of found {@link AccessibilityNodeInfo}s. */ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewTextInActiveWindow( - IAccessibilityServiceConnection connection, String text) { + int connectionId, String text) { try { - final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = connection.findAccessibilityNodeInfosByViewTextInActiveWindow( - text, interactionId, this, Thread.currentThread().getId()); - // If the scale is zero the call has failed. - if (windowScale > 0) { - List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( - interactionId); - finalizeAccessibilityNodeInfos(infos, connection, windowScale); - return infos; + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + final int interactionId = mInteractionIdCounter.getAndIncrement(); + final float windowScale = + connection.findAccessibilityNodeInfosByViewTextInActiveWindow(text, + interactionId, this, Thread.currentThread().getId()); + // If the scale is zero the call has failed. + if (windowScale > 0) { + List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( + interactionId); + finalizeAccessibilityNodeInfos(infos, connectionId, windowScale); + return infos; + } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } } } catch (RemoteException re) { - /* ignore */ + if (DEBUG) { + Log.w(LOG_TAG, "Error while calling remote" + + " findAccessibilityNodeInfosByViewTextInActiveWindow", re); + } } return null; } @@ -198,30 +241,39 @@ public final class AccessibilityInteractionClient * id is specified and starts from the View whose accessibility id is * specified. * - * @param connection A connection for interacting with the system. + * @param connectionId The id of a connection for interacting with the system. * @param text The searched text. * @param accessibilityWindowId A unique window id. * @param accessibilityViewId A unique View accessibility id from where to start the search. * Use {@link android.view.View#NO_ID} to start from the root. * @return A list of found {@link AccessibilityNodeInfo}s. */ - public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText( - IAccessibilityServiceConnection connection, String text, int accessibilityWindowId, - int accessibilityViewId) { + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(int connectionId, + String text, int accessibilityWindowId, int accessibilityViewId) { try { - final int interactionId = mInteractionIdCounter.getAndIncrement(); - final float windowScale = connection.findAccessibilityNodeInfosByViewText(text, - accessibilityWindowId, accessibilityViewId, interactionId, this, - Thread.currentThread().getId()); - // If the scale is zero the call has failed. - if (windowScale > 0) { - List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( - interactionId); - finalizeAccessibilityNodeInfos(infos, connection, windowScale); - return infos; + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + final int interactionId = mInteractionIdCounter.getAndIncrement(); + final float windowScale = connection.findAccessibilityNodeInfosByViewText(text, + accessibilityWindowId, accessibilityViewId, interactionId, this, + Thread.currentThread().getId()); + // If the scale is zero the call has failed. + if (windowScale > 0) { + List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( + interactionId); + finalizeAccessibilityNodeInfos(infos, connectionId, windowScale); + return infos; + } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } } } catch (RemoteException re) { - /* ignore */ + if (DEBUG) { + Log.w(LOG_TAG, "Error while calling remote" + + " findAccessibilityNodeInfosByViewText", re); + } } return Collections.emptyList(); } @@ -229,24 +281,33 @@ public final class AccessibilityInteractionClient /** * Performs an accessibility action on an {@link AccessibilityNodeInfo}. * - * @param connection A connection for interacting with the system. + * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId The id of the window. * @param accessibilityViewId A unique View accessibility id. * @param action The action to perform. * @return Whether the action was performed. */ - public boolean performAccessibilityAction(IAccessibilityServiceConnection connection, - int accessibilityWindowId, int accessibilityViewId, int action) { + public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId, + int accessibilityViewId, int action) { try { - final int interactionId = mInteractionIdCounter.getAndIncrement(); - final boolean success = connection.performAccessibilityAction( - accessibilityWindowId, accessibilityViewId, action, interactionId, this, - Thread.currentThread().getId()); - if (success) { - return getPerformAccessibilityActionResult(interactionId); + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + final int interactionId = mInteractionIdCounter.getAndIncrement(); + final boolean success = connection.performAccessibilityAction( + accessibilityWindowId, accessibilityViewId, action, interactionId, this, + Thread.currentThread().getId()); + if (success) { + return getPerformAccessibilityActionResult(interactionId); + } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } } } catch (RemoteException re) { - /* ignore */ + if (DEBUG) { + Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re); + } } return false; } @@ -406,14 +467,14 @@ public final class AccessibilityInteractionClient * Finalize an {@link AccessibilityNodeInfo} before passing it to the client. * * @param info The info. - * @param connection The current connection to the system. + * @param connectionId The id of the connection to the system. * @param windowScale The source window compatibility scale. */ - private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info, - IAccessibilityServiceConnection connection, float windowScale) { + private void finalizeAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId, + float windowScale) { if (info != null) { applyCompatibilityScaleIfNeeded(info, windowScale); - info.setConnection(connection); + info.setConnectionId(connectionId); info.setSealed(true); } } @@ -422,16 +483,16 @@ public final class AccessibilityInteractionClient * Finalize {@link AccessibilityNodeInfo}s before passing them to the client. * * @param infos The {@link AccessibilityNodeInfo}s. - * @param connection The current connection to the system. + * @param connectionId The id of the connection to the system. * @param windowScale The source window compatibility scale. */ private void finalizeAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos, - IAccessibilityServiceConnection connection, float windowScale) { + int connectionId, float windowScale) { if (infos != null) { final int infosCount = infos.size(); for (int i = 0; i < infosCount; i++) { AccessibilityNodeInfo info = infos.get(i); - finalizeAccessibilityNodeInfo(info, connection, windowScale); + finalizeAccessibilityNodeInfo(info, connectionId, windowScale); } } } @@ -449,4 +510,39 @@ public final class AccessibilityInteractionClient return result; } } + + /** + * Gets a cached accessibility service connection. + * + * @param connectionId The connection id. + * @return The cached connection if such. + */ + public IAccessibilityServiceConnection getConnection(int connectionId) { + synchronized (sConnectionCache) { + return sConnectionCache.get(connectionId); + } + } + + /** + * Adds a cached accessibility service connection. + * + * @param connectionId The connection id. + * @param connection The connection. + */ + public void addConnection(int connectionId, IAccessibilityServiceConnection connection) { + synchronized (sConnectionCache) { + sConnectionCache.put(connectionId, connection); + } + } + + /** + * Removes a cached accessibility service connection. + * + * @param connectionId The connection id. + */ + public void removeConnection(int connectionId) { + synchronized (sConnectionCache) { + sConnectionCache.remove(connectionId); + } + } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index fa34ee747bc4..9b0f44a55614 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -16,7 +16,6 @@ package android.view.accessibility; -import android.accessibilityservice.IAccessibilityServiceConnection; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; @@ -53,6 +52,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final boolean DEBUG = false; + private static final int UNDEFINED = -1; + // Actions. /** @@ -107,9 +108,9 @@ public class AccessibilityNodeInfo implements Parcelable { private boolean mSealed; // Data. - private int mAccessibilityViewId = View.NO_ID; - private int mAccessibilityWindowId = View.NO_ID; - private int mParentAccessibilityViewId = View.NO_ID; + private int mAccessibilityViewId = UNDEFINED; + private int mAccessibilityWindowId = UNDEFINED; + private int mParentAccessibilityViewId = UNDEFINED; private int mBooleanProperties; private final Rect mBoundsInParent = new Rect(); private final Rect mBoundsInScreen = new Rect(); @@ -122,7 +123,7 @@ public class AccessibilityNodeInfo implements Parcelable { private SparseIntArray mChildAccessibilityIds = new SparseIntArray(); private int mActions; - private IAccessibilityServiceConnection mConnection; + private int mConnectionId = UNDEFINED; /** * Hide constructor from clients. @@ -181,7 +182,7 @@ public class AccessibilityNodeInfo implements Parcelable { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mAccessibilityWindowId, childAccessibilityViewId); } @@ -253,7 +254,7 @@ public class AccessibilityNodeInfo implements Parcelable { return false; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.performAccessibilityAction(mConnection, mAccessibilityWindowId, + return client.performAccessibilityAction(mConnectionId, mAccessibilityWindowId, mAccessibilityViewId, action); } @@ -277,7 +278,7 @@ public class AccessibilityNodeInfo implements Parcelable { return Collections.emptyList(); } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfosByViewText(mConnection, text, + return client.findAccessibilityNodeInfosByViewText(mConnectionId, text, mAccessibilityWindowId, mAccessibilityViewId); } @@ -297,7 +298,7 @@ public class AccessibilityNodeInfo implements Parcelable { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mAccessibilityWindowId, mParentAccessibilityViewId); } @@ -755,15 +756,16 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * Sets the connection for interacting with the system. + * Sets the unique id of the IAccessibilityServiceConnection over which + * this instance can send requests to the system. * - * @param connection The client token. + * @param connectionId The connection id. * * @hide */ - public final void setConnection(IAccessibilityServiceConnection connection) { + public void setConnectionId(int connectionId) { enforceNotSealed(); - mConnection = connection; + mConnectionId = connectionId; } /** @@ -900,16 +902,11 @@ public class AccessibilityNodeInfo implements Parcelable { * </p> */ public void writeToParcel(Parcel parcel, int flags) { - if (mConnection == null) { - parcel.writeInt(0); - } else { - parcel.writeInt(1); - parcel.writeStrongBinder(mConnection.asBinder()); - } parcel.writeInt(isSealed() ? 1 : 0); parcel.writeInt(mAccessibilityViewId); parcel.writeInt(mAccessibilityWindowId); parcel.writeInt(mParentAccessibilityViewId); + parcel.writeInt(mConnectionId); SparseIntArray childIds = mChildAccessibilityIds; final int childIdsSize = childIds.size(); @@ -949,10 +946,10 @@ public class AccessibilityNodeInfo implements Parcelable { */ private void init(AccessibilityNodeInfo other) { mSealed = other.mSealed; - mConnection = other.mConnection; mAccessibilityViewId = other.mAccessibilityViewId; mParentAccessibilityViewId = other.mParentAccessibilityViewId; mAccessibilityWindowId = other.mAccessibilityWindowId; + mConnectionId = other.mConnectionId; mBoundsInParent.set(other.mBoundsInParent); mBoundsInScreen.set(other.mBoundsInScreen); mPackageName = other.mPackageName; @@ -970,14 +967,11 @@ public class AccessibilityNodeInfo implements Parcelable { * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}. */ private void initFromParcel(Parcel parcel) { - if (parcel.readInt() == 1) { - mConnection = IAccessibilityServiceConnection.Stub.asInterface( - parcel.readStrongBinder()); - } mSealed = (parcel.readInt() == 1); mAccessibilityViewId = parcel.readInt(); mAccessibilityWindowId = parcel.readInt(); mParentAccessibilityViewId = parcel.readInt(); + mConnectionId = parcel.readInt(); SparseIntArray childIds = mChildAccessibilityIds; final int childrenSize = parcel.readInt(); @@ -1011,10 +1005,10 @@ public class AccessibilityNodeInfo implements Parcelable { */ private void clear() { mSealed = false; - mConnection = null; - mAccessibilityViewId = View.NO_ID; - mParentAccessibilityViewId = View.NO_ID; - mAccessibilityWindowId = View.NO_ID; + mAccessibilityViewId = UNDEFINED; + mParentAccessibilityViewId = UNDEFINED; + mAccessibilityWindowId = UNDEFINED; + mConnectionId = UNDEFINED; mChildAccessibilityIds.clear(); mBoundsInParent.set(0, 0, 0, 0); mBoundsInScreen.set(0, 0, 0, 0); @@ -1048,9 +1042,8 @@ public class AccessibilityNodeInfo implements Parcelable { } private boolean canPerformRequestOverConnection(int accessibilityViewId) { - return (mAccessibilityWindowId != View.NO_ID - && accessibilityViewId != View.NO_ID - && mConnection != null); + return (mConnectionId != UNDEFINED && mAccessibilityWindowId != UNDEFINED + && accessibilityViewId != UNDEFINED); } @Override diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index a4e0688e22fa..18d0f6fe8a27 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -16,7 +16,6 @@ package android.view.accessibility; -import android.accessibilityservice.IAccessibilityServiceConnection; import android.os.Parcelable; import android.view.View; @@ -78,8 +77,8 @@ public class AccessibilityRecord { int mAddedCount= UNDEFINED; int mRemovedCount = UNDEFINED; - int mSourceViewId = View.NO_ID; - int mSourceWindowId = View.NO_ID; + int mSourceViewId = UNDEFINED; + int mSourceWindowId = UNDEFINED; CharSequence mClassName; CharSequence mContentDescription; @@ -87,7 +86,8 @@ public class AccessibilityRecord { Parcelable mParcelableData; final List<CharSequence> mText = new ArrayList<CharSequence>(); - IAccessibilityServiceConnection mConnection; + + int mConnectionId = UNDEFINED; /* * Hide constructor. @@ -108,8 +108,8 @@ public class AccessibilityRecord { mSourceWindowId = source.getAccessibilityWindowId(); mSourceViewId = source.getAccessibilityViewId(); } else { - mSourceWindowId = View.NO_ID; - mSourceViewId = View.NO_ID; + mSourceWindowId = UNDEFINED; + mSourceViewId = UNDEFINED; } } @@ -119,33 +119,21 @@ public class AccessibilityRecord { * <strong>Note:</strong> It is a client responsibility to recycle the received info * by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()} * to avoid creating of multiple instances. - * * </p> * @return The info of the source. */ public AccessibilityNodeInfo getSource() { enforceSealed(); - if (mSourceWindowId == View.NO_ID || mSourceViewId == View.NO_ID || mConnection == null) { + if (mConnectionId == UNDEFINED || mSourceWindowId == UNDEFINED + || mSourceViewId == UNDEFINED) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfoByAccessibilityId(mConnection, mSourceWindowId, + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId, mSourceViewId); } /** - * Sets the connection for interacting with the AccessibilityManagerService. - * - * @param connection The connection. - * - * @hide - */ - public void setConnection(IAccessibilityServiceConnection connection) { - enforceNotSealed(); - mConnection = connection; - } - - /** * Gets the id of the window from which the event comes from. * * @return The window id. @@ -561,6 +549,19 @@ public class AccessibilityRecord { } /** + * Sets the unique id of the IAccessibilityServiceConnection over which + * this instance can send requests to the system. + * + * @param connectionId The connection id. + * + * @hide + */ + public void setConnectionId(int connectionId) { + enforceNotSealed(); + mConnectionId = connectionId; + } + + /** * Sets if this instance is sealed. * * @param sealed Whether is sealed. @@ -708,7 +709,7 @@ public class AccessibilityRecord { mText.addAll(record.mText); mSourceWindowId = record.mSourceWindowId; mSourceViewId = record.mSourceViewId; - mConnection = record.mConnection; + mConnectionId = record.mConnectionId; } /** @@ -732,8 +733,9 @@ public class AccessibilityRecord { mBeforeText = null; mParcelableData = null; mText.clear(); - mSourceViewId = View.NO_ID; - mSourceWindowId = View.NO_ID; + mSourceViewId = UNDEFINED; + mSourceWindowId = UNDEFINED; + mConnectionId = UNDEFINED; } @Override diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index c621ff6d2d60..c3794bec3ef0 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -49,5 +49,5 @@ interface IAccessibilityManager { void removeAccessibilityInteractionConnection(IWindow windowToken); - IAccessibilityServiceConnection registerEventListener(IEventListener client); + void registerEventListener(IEventListener client); } diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index d6e36bb5eeb3..9fa5593e7ad9 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -345,6 +345,11 @@ public final class CookieManager { * a system private class. */ public synchronized void setCookie(WebAddress uri, String value) { + if (JniUtil.useChromiumHttpStack()) { + nativeSetCookie(uri.toString(), value, false); + return; + } + if (value != null && value.length() > MAX_COOKIE_LENGTH) { return; } @@ -500,6 +505,10 @@ public final class CookieManager { * is a system private class. */ public synchronized String getCookie(WebAddress uri) { + if (JniUtil.useChromiumHttpStack()) { + return nativeGetCookie(uri.toString(), false); + } + if (!mAcceptCookie || uri == null) { return null; } @@ -573,6 +582,8 @@ public final class CookieManager { * {@hide} Too late to release publically. */ public void waitForCookieOperationsToComplete() { + // Note that this function is applicable for both the java + // and native http stacks, and works correctly with either. synchronized (this) { while (pendingCookieOperations > 0) { try { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 71551598ae43..24eebd7594b7 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -507,7 +507,7 @@ public class WebView extends AbsoluteLayout private float mLastVelY; // The id of the native layer being scrolled. - private int mScrollingLayer; + private int mCurrentScrollingLayerId; private Rect mScrollingLayerRect = new Rect(); // only trigger accelerated fling if the new velocity is at least @@ -3665,7 +3665,7 @@ public class WebView extends AbsoluteLayout if (x == mScrollingLayerRect.left && y == mScrollingLayerRect.top) { return; } - nativeScrollLayer(mScrollingLayer, x, y); + nativeScrollLayer(mCurrentScrollingLayerId, x, y); mScrollingLayerRect.left = x; mScrollingLayerRect.top = y; onScrollChanged(mScrollX, mScrollY, mScrollX, mScrollY); @@ -4470,6 +4470,7 @@ public class WebView extends AbsoluteLayout Rect vBox = contentToViewRect(contentBounds); Rect visibleRect = new Rect(); calcOurVisibleRect(visibleRect); + offsetByLayerScrollPosition(vBox); // If the textfield is on screen, place the WebTextView in // its new place, accounting for our new scroll/zoom values, // and adjust its textsize. @@ -4505,6 +4506,14 @@ public class WebView extends AbsoluteLayout } } + private void offsetByLayerScrollPosition(Rect box) { + if ((mCurrentScrollingLayerId != 0) + && (mCurrentScrollingLayerId == nativeFocusCandidateLayerId())) { + box.offsetTo(box.left - mScrollingLayerRect.left, + box.top - mScrollingLayerRect.top); + } + } + void setBaseLayer(int layer, Region invalRegion, boolean showVisualIndicator, boolean isPictureAfterFirstLayout, boolean registerPageSwapCallback) { if (mNativeClass == 0) @@ -4923,6 +4932,7 @@ public class WebView extends AbsoluteLayout // should be in content coordinates. Rect bounds = nativeFocusCandidateNodeBounds(); Rect vBox = contentToViewRect(bounds); + offsetByLayerScrollPosition(vBox); mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height()); if (!Rect.intersects(bounds, visibleRect)) { revealSelection(); @@ -5514,10 +5524,10 @@ public class WebView extends AbsoluteLayout mMaxAutoScrollX = getViewWidth(); mMinAutoScrollY = 0; mMaxAutoScrollY = getViewHeightWithTitle(); - mScrollingLayer = nativeScrollableLayer(viewToContentX(mSelectX), + mCurrentScrollingLayerId = nativeScrollableLayer(viewToContentX(mSelectX), viewToContentY(mSelectY), mScrollingLayerRect, mScrollingLayerBounds); - if (mScrollingLayer != 0) { + if (mCurrentScrollingLayerId != 0) { if (mScrollingLayerRect.left != mScrollingLayerRect.right) { mMinAutoScrollX = Math.max(mMinAutoScrollX, contentToViewX(mScrollingLayerBounds.left)); @@ -6003,9 +6013,9 @@ public class WebView extends AbsoluteLayout private void startScrollingLayer(float x, float y) { int contentX = viewToContentX((int) x + mScrollX); int contentY = viewToContentY((int) y + mScrollY); - mScrollingLayer = nativeScrollableLayer(contentX, contentY, + mCurrentScrollingLayerId = nativeScrollableLayer(contentX, contentY, mScrollingLayerRect, mScrollingLayerBounds); - if (mScrollingLayer != 0) { + if (mCurrentScrollingLayerId != 0) { mTouchMode = TOUCH_DRAG_LAYER_MODE; } } @@ -6236,7 +6246,7 @@ public class WebView extends AbsoluteLayout ted.mPointsInView[0] = new Point(x, y); ted.mMetaState = ev.getMetaState(); ted.mReprocess = mDeferTouchProcess; - ted.mNativeLayer = mScrollingLayer; + ted.mNativeLayer = mCurrentScrollingLayerId; ted.mNativeLayerRect.set(mScrollingLayerRect); ted.mSequence = mTouchEventQueue.nextTouchSequence(); mTouchEventQueue.preQueueTouchEventData(ted); @@ -6427,7 +6437,7 @@ public class WebView extends AbsoluteLayout ted.mPointsInView[0] = new Point(x, y); ted.mMetaState = ev.getMetaState(); ted.mReprocess = mDeferTouchProcess; - ted.mNativeLayer = mScrollingLayer; + ted.mNativeLayer = mCurrentScrollingLayerId; ted.mNativeLayerRect.set(mScrollingLayerRect); ted.mSequence = mTouchEventQueue.nextTouchSequence(); mTouchEventQueue.preQueueTouchEventData(ted); @@ -6736,7 +6746,7 @@ public class WebView extends AbsoluteLayout // directions. mTouchMode might be TOUCH_DRAG_MODE if we have // reached the edge of a layer but mScrollingLayer will be non-zero // if we initiated the drag on a layer. - if (mScrollingLayer != 0) { + if (mCurrentScrollingLayerId != 0) { final int contentX = viewToContentDimension(deltaX); final int contentY = viewToContentDimension(deltaY); @@ -7258,7 +7268,7 @@ public class WebView extends AbsoluteLayout + " vx=" + vx + " vy=" + vy + " maxX=" + maxX + " maxY=" + maxY + " scrollX=" + scrollX + " scrollY=" + scrollY - + " layer=" + mScrollingLayer); + + " layer=" + mCurrentScrollingLayerId); } // Allow sloppy flings without overscrolling at the edges. @@ -8367,7 +8377,7 @@ public class WebView extends AbsoluteLayout mSentAutoScrollMessage = false; break; } - if (mScrollingLayer == 0) { + if (mCurrentScrollingLayerId == 0) { pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0); } else { scrollLayerTo(mScrollingLayerRect.left + mAutoScrollX, @@ -9581,6 +9591,7 @@ public class WebView extends AbsoluteLayout * See WebTextView.setType() */ private native int nativeFocusCandidateType(); + private native int nativeFocusCandidateLayerId(); private native boolean nativeFocusIsPlugin(); private native Rect nativeFocusNodeBounds(); /* package */ native int nativeFocusNodePointer(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0f1b43bf5ab2..bd03fb908838 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2581,6 +2581,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Returns whether the text is allowed to be wider than the View is. + * If false, the text will be wrapped to the width of the View. + * + * @attr ref android.R.styleable#TextView_scrollHorizontally + * @hide + */ + public boolean getHorizontallyScrolling() { + return mHorizontallyScrolling; + } + + /** * Makes the TextView at least this many lines tall. * * Setting this value overrides any other (minimum) height setting. A single line TextView will @@ -3816,7 +3827,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null && imm.isActive(this)) { imm.hideSoftInputFromWindow(getWindowToken(), 0); } - clearFocus(); return; } } @@ -3838,7 +3848,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener | KeyEvent.FLAG_EDITOR_ACTION))); } } - + /** * Set the private content type of the text, which is the * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} @@ -5594,8 +5604,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.extras = mInputContentType.extras; } else { outAttrs.imeOptions = EditorInfo.IME_NULL; - // May not be defined otherwise and needed by onEditorAction - mInputContentType = new InputContentType(); } if (focusSearch(FOCUS_DOWN) != null) { outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; @@ -9370,40 +9378,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mPositionY = mTempCoords[1]; } - public boolean isVisible(int positionX, int positionY) { - final TextView textView = TextView.this; + public void onScrollChanged() { + mScrollHasChanged = true; + } + } - if (mTempRect == null) mTempRect = new Rect(); - final Rect clip = mTempRect; - clip.left = getCompoundPaddingLeft(); - clip.top = getExtendedPaddingTop(); - clip.right = textView.getWidth() - getCompoundPaddingRight(); - clip.bottom = textView.getHeight() - getExtendedPaddingBottom(); - - final ViewParent parent = textView.getParent(); - if (parent == null || !parent.getChildVisibleRect(textView, clip, null)) { - return false; - } + private boolean isPositionVisible(int positionX, int positionY) { + synchronized (sTmpPosition) { + final float[] position = sTmpPosition; + position[0] = positionX; + position[1] = positionY; + View view = this; + + while (view != null) { + if (view != this) { + // Local scroll is already taken into account in positionX/Y + position[0] -= view.getScrollX(); + position[1] -= view.getScrollY(); + } - int posX = mPositionX + positionX; - int posY = mPositionY + positionY; + if (position[0] < 0 || position[1] < 0 || + position[0] > view.getWidth() || position[1] > view.getHeight()) { + return false; + } - // Offset by 1 to take into account 0.5 and int rounding around getPrimaryHorizontal. - return posX >= clip.left - 1 && posX <= clip.right + 1 && - posY >= clip.top && posY <= clip.bottom; - } + if (!view.getMatrix().isIdentity()) { + view.getMatrix().mapPoints(position); + } - public boolean isOffsetVisible(int offset) { - final int line = mLayout.getLineForOffset(offset); - final int lineBottom = mLayout.getLineBottom(line); - final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset); - return isVisible(primaryHorizontal + viewportToContentHorizontalOffset(), - lineBottom + viewportToContentVerticalOffset()); - } + position[0] += view.getLeft(); + position[1] += view.getTop(); - public void onScrollChanged() { - mScrollHasChanged = true; + final ViewParent parent = view.getParent(); + if (parent instanceof View) { + view = (View) parent; + } else { + // We've reached the ViewRoot, stop iterating + view = null; + } + } } + + // We've been able to walk up the view hierarchy and the position was never clipped + return true; + } + + private boolean isOffsetVisible(int offset) { + final int line = mLayout.getLineForOffset(offset); + final int lineBottom = mLayout.getLineBottom(line); + final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset); + return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(), + lineBottom + viewportToContentVerticalOffset()); } @Override @@ -9504,7 +9529,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled) { // Either parentPositionChanged or parentScrolled is true, check if still visible - if (isShowing() && getPositionListener().isOffsetVisible(getTextOffset())) { + if (isShowing() && isOffsetVisible(getTextOffset())) { if (parentScrolled) computeLocalPosition(); updatePosition(parentPositionX, parentPositionY); } else { @@ -10528,7 +10553,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - return getPositionListener().isVisible(mPositionX + mHotspotX, mPositionY); + return TextView.this.isPositionVisible(mPositionX + mHotspotX, mPositionY); } public abstract int getCurrentCursorOffset(); @@ -11510,6 +11535,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Path mHighlightPath; private boolean mHighlightPathBogus = true; private static final RectF sTempRect = new RectF(); + private static final float[] sTmpPosition = new float[2]; // XXX should be much larger private static final int VERY_WIDE = 1024*1024; diff --git a/core/res/res/layout/global_actions_item.xml b/core/res/res/layout/global_actions_item.xml index 67b164479cbe..13ab98513e8b 100644 --- a/core/res/res/layout/global_actions_item.xml +++ b/core/res/res/layout/global_actions_item.xml @@ -14,48 +14,46 @@ limitations under the License. --> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<!-- RelativeLayouts have an issue enforcing minimum heights, so just + work around this for now with LinearLayouts. --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="?android:attr/listPreferredItemHeight" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" android:paddingLeft="11dip" android:paddingTop="6dip" android:paddingBottom="6dip" > - <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_alignParentTop="true" - android:layout_alignParentBottom="true" + android:layout_gravity="center" android:layout_marginRight="9dip" - /> - - - <TextView android:id="@+id/status" - android:layout_width="match_parent" - android:layout_height="26dip" - - android:layout_toRightOf="@id/icon" - android:layout_alignParentBottom="true" - android:layout_alignParentRight="true" - - android:textAppearance="?android:attr/textAppearanceSmall" /> - - <TextView android:id="@+id/message" + <LinearLayout + android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + > + <TextView android:id="@+id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top" + + android:gravity="center_vertical" + android:textAppearance="?android:attr/textAppearanceLarge" + /> + <TextView android:id="@+id/status" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:minHeight="26dp" - android:layout_toRightOf="@id/icon" - android:layout_alignParentRight="true" - android:layout_alignParentTop="true" - android:layout_above="@id/status" - android:layout_alignWithParentIfMissing="true" - android:gravity="center_vertical" - android:textAppearance="?android:attr/textAppearanceLarge" - /> - - -</RelativeLayout> + android:textAppearance="?android:attr/textAppearanceSmall" + /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 25f7d2565c92..767cafe01548 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -741,7 +741,4 @@ <string name="config_wimaxServiceClassname"></string> <!-- Name of the wimax state tracker clas --> <string name="config_wimaxStateTrackerClassname"></string> - - <!-- Name of screensaver components to look for if none has been chosen by the user --> - <string name="config_defaultDreamComponent">com.google.android.deskclock/com.android.deskclock.Screensaver</string> </resources> diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java index 3521296713e1..ec1212425491 100644 --- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java @@ -26,6 +26,7 @@ import android.os.SystemClock; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; +import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityManager; @@ -54,28 +55,31 @@ public class InterrogationActivityTest // Timeout before give up wait for the system to process an accessibility setting change. private static final int TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING = 2000; + // Timeout for the accessibility state of an Activity to be fully initialized. + private static final int TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS = 100; + // Handle to a connection to the AccessibilityManagerService - private static IAccessibilityServiceConnection sConnection; + private static int sConnectionId = View.NO_ID; // The last received accessibility event - private static volatile AccessibilityEvent sLastFocusAccessibilityEvent; + private volatile AccessibilityEvent mLastAccessibilityEvent; public InterrogationActivityTest() { super(InterrogationActivity.class); } + @Override + public void setUp() throws Exception { + ensureConnection(); + bringUpActivityWithInitalizedAccessbility(); + } + @LargeTest public void testFindAccessibilityNodeInfoByViewId() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); - AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertNotNull(button); assertEquals(0, button.getChildCount()); @@ -120,15 +124,9 @@ public class InterrogationActivityTest public void testFindAccessibilityNodeInfoByViewText() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); - // find a view by text - List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfosByViewTextInActiveWindow(connection, "butto"); + List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance() + .findAccessibilityNodeInfosByViewTextInActiveWindow(sConnectionId, "butto"); assertEquals(9, buttons.size()); } finally { if (DEBUG) { @@ -143,15 +141,11 @@ public class InterrogationActivityTest public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); + bringUpActivityWithInitalizedAccessbility(); // find a view by text - List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfosByViewTextInActiveWindow(connection, + List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance() + .findAccessibilityNodeInfosByViewTextInActiveWindow(sConnectionId, "contentDescription"); assertEquals(1, buttons.size()); } finally { @@ -167,12 +161,6 @@ public class InterrogationActivityTest public void testTraverseAllViews() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); - // make list of expected nodes List<String> classNameAndTextList = new ArrayList<String>(); classNameAndTextList.add("android.widget.LinearLayout"); @@ -190,7 +178,7 @@ public class InterrogationActivityTest classNameAndTextList.add("android.widget.ButtonButton9"); AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.root); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.root); assertNotNull("We must find the existing root.", root); Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); @@ -227,23 +215,17 @@ public class InterrogationActivityTest public void testPerformAccessibilityActionFocus() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); - // find a view and make sure it is not focused AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertFalse(button.isFocused()); // focus the view assertTrue(button.performAction(ACTION_FOCUS)); // find the view again and make sure it is focused - button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + button = AccessibilityInteractionClient.getInstance() + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertTrue(button.isFocused()); } finally { if (DEBUG) { @@ -257,15 +239,9 @@ public class InterrogationActivityTest public void testPerformAccessibilityActionClearFocus() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); - // find a view and make sure it is not focused AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertFalse(button.isFocused()); // focus the view @@ -273,7 +249,7 @@ public class InterrogationActivityTest // find the view again and make sure it is focused button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertTrue(button.isFocused()); // unfocus the view @@ -281,7 +257,7 @@ public class InterrogationActivityTest // find the view again and make sure it is not focused button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertFalse(button.isFocused()); } finally { if (DEBUG) { @@ -296,15 +272,9 @@ public class InterrogationActivityTest public void testPerformAccessibilityActionSelect() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); - // find a view and make sure it is not selected AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertFalse(button.isSelected()); // select the view @@ -312,7 +282,7 @@ public class InterrogationActivityTest // find the view again and make sure it is selected button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertTrue(button.isSelected()); } finally { if (DEBUG) { @@ -326,15 +296,9 @@ public class InterrogationActivityTest public void testPerformAccessibilityActionClearSelection() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); - // find a view and make sure it is not selected AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertFalse(button.isSelected()); // select the view @@ -342,15 +306,15 @@ public class InterrogationActivityTest // find the view again and make sure it is selected button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertTrue(button.isSelected()); // unselect the view assertTrue(button.performAction(ACTION_CLEAR_SELECTION)); // find the view again and make sure it is not selected - button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + button = AccessibilityInteractionClient.getInstance() + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertFalse(button.isSelected()); } finally { if (DEBUG) { @@ -365,30 +329,24 @@ public class InterrogationActivityTest public void testAccessibilityEventGetSource() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); - // find a view and make sure it is not focused AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); assertFalse(button.isSelected()); // focus the view assertTrue(button.performAction(ACTION_FOCUS)); - synchronized (sConnection) { + synchronized (this) { try { - sConnection.wait(500); + wait(TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS); } catch (InterruptedException ie) { /* ignore */ } } // check that last event source - AccessibilityNodeInfo source = sLastFocusAccessibilityEvent.getSource(); + AccessibilityNodeInfo source = mLastAccessibilityEvent.getSource(); assertNotNull(source); // bounds @@ -430,15 +388,9 @@ public class InterrogationActivityTest public void testObjectContract() throws Exception { final long startTimeMillis = SystemClock.uptimeMillis(); try { - // hook into the system first - IAccessibilityServiceConnection connection = getConnection(); - - // bring up the activity - getActivity(); - // find a view and make sure it is not focused AccessibilityNodeInfo button = AccessibilityInteractionClient.getInstance() - .findAccessibilityNodeInfoByViewIdInActiveWindow(connection, R.id.button5); + .findAccessibilityNodeInfoByViewIdInActiveWindow(sConnectionId, R.id.button5); AccessibilityNodeInfo parent = button.getParent(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { @@ -459,24 +411,57 @@ public class InterrogationActivityTest } } - @Override - protected void scrubClass(Class<?> testCaseClass) { - /* intentionally do not scrub */ + private void bringUpActivityWithInitalizedAccessbility() { + mLastAccessibilityEvent = null; + // bring up the activity + getActivity(); + + final long startTimeMillis = SystemClock.uptimeMillis(); + while (true) { + if (mLastAccessibilityEvent != null) { + final int eventType = mLastAccessibilityEvent.getEventType(); + if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + return; + } + } + final long remainingTimeMillis = TIMEOUT_ACCESSIBLITY_STATE_INITIALIZED_MILLIS + - (SystemClock.uptimeMillis() - startTimeMillis); + if (remainingTimeMillis <= 0) { + return; + } + synchronized (this) { + try { + wait(remainingTimeMillis); + } catch (InterruptedException e) { + /* ignore */ + } + } + } } - private IAccessibilityServiceConnection getConnection() throws Exception { - if (sConnection == null) { + private void ensureConnection() throws Exception { + if (sConnectionId == View.NO_ID) { IEventListener listener = new IEventListener.Stub() { - public void setConnection(IAccessibilityServiceConnection connection) {} + public void setConnection(IAccessibilityServiceConnection connection, + int connectionId) { + sConnectionId = connectionId; + if (connection != null) { + AccessibilityInteractionClient.getInstance().addConnection(connectionId, + connection); + } else { + AccessibilityInteractionClient.getInstance().removeConnection(connectionId); + } + synchronized (this) { + notifyAll(); + } + } public void onInterrupt() {} public void onAccessibilityEvent(AccessibilityEvent event) { - if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) { - sLastFocusAccessibilityEvent = AccessibilityEvent.obtain(event); - } - synchronized (sConnection) { - sConnection.notifyAll(); + mLastAccessibilityEvent = AccessibilityEvent.obtain(event); + synchronized (this) { + notifyAll(); } } }; @@ -485,28 +470,11 @@ public class InterrogationActivityTest AccessibilityManager.getInstance(getInstrumentation().getContext()); synchronized (this) { - if (!accessibilityManager.isEnabled()) { - // Make sure we wake ourselves as the desired state is propagated. - accessibilityManager.addAccessibilityStateChangeListener( - new AccessibilityManager.AccessibilityStateChangeListener() { - public void onAccessibilityStateChanged(boolean enabled) { - synchronized (this) { - notifyAll(); - } - } - }); - IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( + IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); - sConnection = manager.registerEventListener(listener); - - wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING); - } else { - IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( - ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); - sConnection = manager.registerEventListener(listener); - } + manager.registerEventListener(listener); + wait(TIMEOUT_PROPAGATE_ACCESSIBLITY_SETTING); } } - return sConnection; } } diff --git a/docs/html/guide/market/billing/billing_admin.jd b/docs/html/guide/market/billing/billing_admin.jd index cbb4b29cfc47..a84eb4e88139 100755 --- a/docs/html/guide/market/billing/billing_admin.jd +++ b/docs/html/guide/market/billing/billing_admin.jd @@ -39,12 +39,12 @@ few administrative tasks, including setting up and maintaining your product list site, registering test accounts, and handling refunds when necessary.</p> <p>You must have an Android Market publisher account to register test accounts. And you must have a -Google Checkout Merchant account to create a product list and issue refunds to your users. If you +Google Checkout merchant account to create a product list and issue refunds to your users. If you already have a publisher account on Android Market, you can use your existing account. You do not need to register for a new account to support in-app billing. If you do not have a publisher account, you can register as an Android Market developer and set up a publisher account at the Android Market <a href="http://market.android.com/publish">publisher site</a>. If you do not have a -Google Checkout Merchant account, you can register for one at the <a +Google Checkout merchant account, you can register for one at the <a href="http://checkout.google.com">Google Checkout site</a>.</p> <h2 id="billing-list-setup">Creating a Product List</h2> @@ -57,7 +57,7 @@ items that are listed in another application's product list.</p> <p>You can access an application's product list by clicking the <strong>In-App Products</strong> link that appears under each of the applications that are listed for your publisher account (see figure 1). The <strong>In-App Products</strong> link appears only if you have a Google Checkout -Merchant account and an application's manifest includes the <code>com.android.vending.BILLING</code> +merchant account and an application's manifest includes the <code>com.android.vending.BILLING</code> permission.</p> <img src="{@docRoot}images/billing_product_list_entry.png" height="548" id="figure1" /> @@ -71,20 +71,37 @@ product description, and price (see figure 2). The product list stores only meta you are selling in your application. It does not store any digital content. You are responsible for storing and delivering the digital content that you sell in your applications.</p> -<img src="{@docRoot}images/billing_product_list.png" height="560" id="figure2" /> +<img src="{@docRoot}images/billing_product_list.png" height="658" id="figure2" /> <p class="img-caption"> <strong>Figure 2.</strong> An application's product list. </p> -<p>You can create a product list for a published application or a draft application that's been -uploaded and saved to the Android Market site. However, you must have a Google Checkout Merchant +<p>You can create a product list for any published application or any draft application that's been +uploaded and saved to the Android Market site. However, you must have a Google Checkout merchant account and the application's manifest must include the <code>com.android.vending.BILLING</code> permission. If an application's manifest does not include this permission, you will be able to edit existing items in the product list but you will not be able to add new items to the list. For more -information, see <a href="#billing-permission">Modifying your application's AndroidManifest.xml -file</a>.</p> +information about this permission, see +<a href="{@docRoot}guide/market/billing/billing_integrate.html#billing-permission">Updating Your +Application's Manifest</a>.</p> -<p>To create a product list for an application, follow these steps:</p> +<p>In addition, an application package can have only one product list. If you create a product +list for an application, and you use the <a +href="{@docRoot}guide/market/publishing/multiple-apks.html">multiple APK feature</a> to distribute +more than one APK for that application, the product list applies to all APK versions that are +associated with the application listing. You cannot create individual product lists for each APK if +you are using the multiple APK feature.</p> + +<p>You can add items to a product list two ways: you can add items one at a time by using the In-app +Products UI (see figure 3), or you can add a batch of items by importing the items from a +comma-separated values (CSV) file (see figure 2). Adding items one at a time is useful if your +application has only a few in-app items or you are adding only a few items to a +product list for testing purposes. The CSV file method is useful if your application has a large +number of in-app items.</p> + +<h3 id="billing-form-add">Adding items one at a time to a product list</h3> + +<p>To add an item to a product list using the In-app Products UI, follow these steps:</p> <ol> <li><a href="http://market.android.com/publish">Log in</a> to your publisher account.</li> @@ -92,7 +109,7 @@ file</a>.</p> <strong>In-app Products</strong>.</li> <li>On the In-app Products List page, click <strong>Add in-app product</strong>.</li> <li>On the Create New In-app Product page (see figure 3), provide details about the item you are - selling and then click <strong>Save</strong>.</li> + selling and then click <strong>Save</strong> or <strong>Publish</strong>.</li> </ol> <img src="{@docRoot}images/billing_list_form.png" height="840" id="figure3" /> @@ -109,25 +126,31 @@ file</a>.</p> (0-9), underlines (_), and dots (.). The product ID "android.test" is reserved, as are all product IDs that start with "android.test."</p> <p>In addition, you cannot modify an item's product ID after it is created, and you cannot reuse - a product ID, even if you delete the item previously using the product ID.</p> + a product ID.</p> </li> - <li><strong>Purchase type</strong> - <p>The purchase type can be "managed per user account" or "unmanaged." You can specify an item's - purchase type only through the publisher site and you can never change an item's purchase type - once you specify it. For more information, see <a href="#billing_purchase_type">Choosing a - purchase type</a> later in this document.</p> + <li><strong>Purchase Type</strong> + <p>The purchase type can be <strong>Managed per user account</strong> or <strong> + Unmanaged</strong>. You can never change an item's purchase type after you set it. For more + information, see <a href="#billing-purchase-type">Choosing a purchase type</a> later in this + document.</p> </li> <li><strong>Publishing State</strong> - <p>An item's publishing state can be "published" or "unpublished." However, to be visible to a - user during checkout, an item's publishing state must be set to "published" and the item's - application must be published on Android Market.</p> + <p>An item's publishing state can be <strong>Published</strong> or <strong>Unpublished + </strong>. To be visible to a user during checkout, an item's publishing state must be set to + <strong>Published</strong> and the item's application must be published on Android Market.</p> <p class="note"><strong>Note:</strong> This is not true for test accounts. An item is visible to a test account if the application is not published and the item is published. See <a href="{@docRoot}guide/market/billing/billing_testing.html#billing-testing-real">Testing In-app Billing</a> for more information.</p> </li> <li><strong>Language</strong> - <p>A product list inherits its language from the parent application.</p> + <p>The language setting determines which languages are used to display the item title and + item description during checkout. A product list inherits its default language from the + parent application. You can add more languages by clicking <strong>add language</strong>. You + can also choose to have the title and description automatically translated from the default + language by selecting the <strong>Fill fields with auto translation</strong> checkbox (see + figure 4). If you do not use the auto translation feature, you must provide the translated + versions of the title and description.</p> </li> <li><strong>Title</strong> <p>The title is a short descriptor for the item. For example, "Sleeping potion." Titles must be @@ -141,9 +164,20 @@ file</a>.</p> visible to users during checkout. Descriptions can be up to 80 characters in length.</p> </li> <li><strong>Price</strong> - <p>Every item must have a price greater than zero; you cannot set a price of "0" (free).</p> + <p>You must provide a default price in your home currency. You can also provide prices in other + currencies, but you can do this only if a currency's corresponding country is listed as a + target country for your application. You can specify target countries on the Edit Application + page in the Android Market developer console.</p> + <p>To specify prices in other currencies, you can manually enter the price for each + currency or you can click <strong>Auto Fill</strong> and let Android Market do a one-time + conversion from your home currency to the currencies you are targeting (see figure 4).</p> </li> </ul> +<img src="{@docRoot}images/billing_list_form_2.png" height="1226" id="figure4" /> +<p class="img-caption"> + <strong>Figure 4.</strong> Specifying additional currencies and additional languages for the + item title and description. +</p> <p>For more information about product IDs and product lists, see <a href="http://market.android.com/support/bin/answer.py?answer=1072599">Creating In-App Product @@ -154,6 +188,197 @@ Pricing</a>.</p> <p class="note"><strong>Note</strong>: Be sure to plan your product ID namespace. You cannot reuse or modify product IDs after you save them.</p> +<h3 id="billing-bulk-add">Adding a batch of items to a product list</h3> + +<p>To add a batch of items to a product list using a CSV file, you first need to create your CSV +file. The data values that you specify in the CSV file represent the same data values you specify +manually through the In-app Products UI (see <a href="#billing-form-add">Adding items one at a time +to a product list</a>). The CSV file uses commas (,) and semi-colons (;) to separate data values. +Commas are used to separate primary data values, and semi-colons are used to separate subvalues. For +example, the syntax for the CSV file is as follows:</p> + +<p>"<em>product_id</em>","<em>publish_state</em>","<em>purchase_type</em>","<em>autotranslate</em> +","<em>locale</em>; <em>title</em>; <em>description</em>","<em>autofill</em>","<em>country</em>; +<em>price</em>" +</p> + +<p>Descriptions and usage details are provided below.</p> + +<ul> + <li><em>product_id</em> + <p>This is equivalent to the In-app Product ID setting in the In-app Products UI. If you specify + a <em>product_id</em> that already exists in a product list, and you choose to overwrite + the product list while importing the CSV file, the data for the existing item is overwritten with + the values specified in the CSV file. The overwrite feature does not delete items that are on a + product list but not present in the CSV file.</p> + </li> + <li><em>publish_state</em> + <p>This is equivalent to the Publishing State setting in the In-app Products UI. Can be <code> + published</code> or <code>unpublished</code>.</p> + </li> + <li><em>purchase_type</em> + <p>This is equivalent to the Purchase Type setting in the In-app Products UI. Can be <code> + managed_by_android</code>, which is equivalent to <strong>Managed per user account + </strong> in the In-app Products UI, or <code>managed_by_publisher</code>, which is equivalent + to <strong>Unmanaged</strong> in the In-app Products UI.</p> + </li> + <li><em>autotranslate</em> + <p>This is equivalent to selecting the <strong>Fill fields with auto translation</strong> + checkbox in the In-app Products UI. Can be <code>true</code> or <code>false</code>.</p> + </li> + <li><em>locale</em> + <p>This is equivalent to the Language setting in the In-app Products UI. You must have an entry + for the default locale. The default locale must be the first entry in the list of + locales, and it must include a <em>title</em> and <em>description</em>. If you want to provide + translated versions of the <em>title</em> and <em>description</em> in addition to the default, + you must use the following syntax rules:</p> + <p>If <em>autotranslate</em> is <code>true</code>, you must specify the default locale, + default title, default description, and other locales using the following format:</p> + <p>"true,"<em>default_locale</em>; <em>default_locale_title</em>; + <em>default_locale_description</em>; <em>locale_2</em>; <em>locale_3</em>, ..."</p> + <p>If <em>autotranslate</em> is <code>false</code>, you must specify the default locale, + default title, and default description as well as the translated titles and descriptions using + the following format:</p> + <p>"false,"<em>default_locale</em>; <em>default_locale_title</em>; + <em>default_locale_description</em>; <em>locale_2</em>; <em>locale_2_title</em>; + <em>local_2_description</em>; <em>locale_3</em>; <em>locale_3_title</em>; + <em>locale_3_description</em>; ..."</p> + <p>See table 1 for a list of the language codes you can use with the <em>locale</em> field.</p> + </li> + <li><em>title</em> + <p>This is equivalent to the Title setting in the In-app Products UI. If the <em>title</em> + contains a semicolon, it must be escaped with a backslash (for example, "\;"). A backslash + should also be escaped with a backslash (for example, "\\">.</p> + </li> + <li><em>description</em> + <p>This is equivalent to the Description in the In-app Products UI. If the <em>description</em> + contains a semicolon, it must be escaped with a backslash (for example, "\;"). A backslash + should also be escaped with a backslash (for example, "\\">.</p> + </li> + <li><em>autofill</em> + <p>This is equivalent to clicking <strong>Auto Fill</strong> in the In-app Products UI. Can be + <code>true</code> or <code>false</code>. The syntax for specifying the <em>country</em> + and <em>price</em> varies depending on which <em>autofill</em> setting you use.</p> + <p>If <em>autofill</em> is set to <code>true</code>, you need to specify only the default + price in your home currency and you must use this syntax:</p> + <p>"true","<em>default_price_in_home_currency</em>" + <p>If <em>autofill</em> is set to <code>false</code>, you need to specify a <em>country</em> + and a <em>price</em> for each currency and you must use the following syntax:</p> + <p>"false", "<em>home_country</em>; <em>default_price_in_home_currency</em>; <em>country_2</em>; + <em>country_2_price</em>; <em>country_3</em>; <em>country_3_price</em>; ..."</p> + </li> + <li><em>country</em> + <p>The country for which you are specifying a price. You can only list countries that your + application is targeting. The country codes are two-letter uppercase + ISO country codes (such as "US") as defined by + <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166-2</a>.</p> + </li> + <li><em>price</em> + <p>This is equivalent to the Price in the In-app Products UI. The price must be specified in + micro-units. To convert a currency value to micro-units, you multiply the real value by 1,000,000. + For example, if you want to sell an in-app item for $1.99 you specify 1990000 in the + <em>price</em> field.</p> + </li> +</ul> + +<p class="table-caption" id="language-table"><strong>Table 1.</strong> Language codes you can use +with the <em>locale</em> field.</p> + +<table> + +<tr> +<th>Language</th> +<th>Code</th> +<th>Language</th> +<th>Code</th> +</tr> +<tr> +<td>Chinese</td> +<td>zh_TW</td> +<td>Italian</td> +<td>it_IT</td> +</tr> +<tr> +<td>Czech</td> +<td>cs_CZ</td> +<td>Japanese</td> +<td>ja_JP</td> +</tr> +<tr> +<td>Danish</td> +<td>da_DK</td> +<td>Korean</td> +<td>ko_KR</td> +</tr> +<tr> +<td>Dutch</td> +<td>nl_NL</td> +<td>Norwegian</td> +<td>no_NO</td> +</tr> +<tr> +<td>English</td> +<td>en_US</td> +<td>Polish</td> +<td>pl_PL</td> +</tr> +<tr> +<td>French</td> +<td>fr_FR</td> +<td>Portuguese</td> +<td>pt_PT</td> +</tr> +<tr> +<td>Finnish</td> +<td>fi_FI</td> +<td>Russian</td> +<td>ru_RU</td> +</tr> +<tr> +<td>German</td> +<td>de_DE</td> +<td>Spanish</td> +<td>es_ES</td> +</tr> +<tr> +<td>Hebrew</td> +<td>iw_IL</td> +<td>Swedish</td> +<td>sv_SE</td> +</tr> +<tr> +<td>Hindi</td> +<td>hi_IN</td> +<td>--</td> +<td>--</td> +</tr> +</table> + +<p>To import the items that are specified in your CSV file, do the following:</p> + +<ol> + <li><a href="http://market.android.com/publish">Log in</a> to your publisher account.</li> + <li>In the <strong>All Android Market listings</strong> panel, under the application name, click + <strong>In-app Products</strong>.</li> + <li>On the In-app Products List page, click <strong>Choose File</strong> and select your CSV +file. + <p>The CSV file must be on your local computer or on a local disk that is connected to your + computer.</p> + </li> + <li>Select the <strong>Overwrite</strong> checkbox if you want to overwrite existing items in + your product list. + <p>This option overwrites values of existing items only if the value of the <em>product_id</em> + in the CSV file matches the In-app Product ID for an existing item in the product list. + Overwriting does not delete items that are on a product list but not present in the CSV + file.</p> + </li> + <li>On the In-app Products List page, click <strong>Import from CSV</strong>.</li> +</ol> + +<p>You can also export an existing product list to a CSV file by clicking <strong>Export to CSV +</strong> on the In-app Product List page. This is useful if you have manually added items to +a product list and you want to start managing the product list through a CSV file.</p> + <h3 id="billing-purchase-type">Choosing a Purchase Type</h3> <p>An item's purchase type controls how Android Market manages the purchase of the item. There are @@ -194,7 +419,7 @@ times.</p> <p>In-app billing does not allow users to send a refund request to Android Market. Refunds for in-app purchases must be directed to you (the application developer). You can then process the -refund through your Google Checkout Merchant account. When you do this, Android Market receives a +refund through your Google Checkout merchant account. When you do this, Android Market receives a refund notification from Google Checkout, and Android Market sends a refund message to your application. For more information, see <a href="{@docRoot}guide/market/billing/billing_overview.html#billing-action-notify">Handling @@ -236,15 +461,15 @@ accounts yourself and distribute the credentials to your developers or testers.< <li><a href="http://market.android.com/publish">Log in</a> to your publisher account.</li> <li>On the upper left part of the page, under your name, click <strong>Edit profile</strong>.</li> <li>On the Edit Profile page, scroll down to the Licensing & In-app Billing panel (see figure - 4).</li> + 5).</li> <li>In Test Accounts, add the email addresses for the test accounts you want to register, separating each account with a comma.</li> <li>Click <strong>Save</strong> to save your profile changes.</li> </ol> -<img src="{@docRoot}images/billing_public_key.png" height="510" id="figure4" /> +<img src="{@docRoot}images/billing_public_key.png" height="510" id="figure5" /> <p class="img-caption"> - <strong>Figure 4.</strong> The Licensing and In-app Billing panel of your account's Edit Profile + <strong>Figure 5.</strong> The Licensing and In-app Billing panel of your account's Edit Profile page lets you register test accounts. </p> diff --git a/docs/html/guide/practices/ui_guidelines/widget_design.jd b/docs/html/guide/practices/ui_guidelines/widget_design.jd index de20e007f393..f63f3c4a8a76 100644 --- a/docs/html/guide/practices/ui_guidelines/widget_design.jd +++ b/docs/html/guide/practices/ui_guidelines/widget_design.jd @@ -250,13 +250,15 @@ android.widget.FrameLayout}. Just as your activity layouts must adapt to differe sizes, widget layouts must adapt to different Home screen grid cell sizes.</p> <p>Below is an example layout that a music widget showing text information and two buttons can use. -It builds upon the previous discussion of adding margins depending on OS version.</p> +It builds upon the previous discussion of adding margins depending on OS version. Note that the +most robust and resilient way to add margins to the widget is to wrap the widget frame and contents +in a padded {@link android.widget.FrameLayout}.</p> <pre> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_margin="@dimen/widget_margin"> + android:padding="@dimen/widget_margin"> <LinearLayout android:layout_width="match_parent" @@ -295,16 +297,16 @@ use flexible layouts attributes like so:</p> <p>When a user adds the widget to their home screen, on an example Android 4.0 device where each -grid cell is 80dp × 100dp in size and 16dp of margins are automatically applied on all sizes, +grid cell is 80dp × 100dp in size and 8dp of margins are automatically applied on all sizes, the widget will be stretched, like so:</p> <img src="{@docRoot}images/widget_design/music_example_stretched.png" - alt="Music widget sitting on an example 80dp x 100dp grid with 16dp of automatic margins + alt="Music widget sitting on an example 80dp x 100dp grid with 8dp of automatic margins added by the system" id="music_example_stretched"> <p class="img-caption"><strong>Figure 7.</strong> Music widget sitting on an example 80dp x 100dp -grid with 16dp of automatic margins added by the system.</p> +grid with 8dp of automatic margins added by the system.</p> <h2 id="templates">Using the App Widget Templates Pack</h2> diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd index 61337b782a75..2cb23c1cdbba 100644 --- a/docs/html/guide/topics/appwidgets/index.jd +++ b/docs/html/guide/topics/appwidgets/index.jd @@ -346,7 +346,7 @@ following layout classes:</p> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" - <strong>android:layout_margin="@dimen/widget_margin"></strong> + <strong>android:padding="@dimen/widget_margin"></strong> <LinearLayout android:layout_width="match_parent" @@ -363,7 +363,7 @@ following layout classes:</p> <li>Create two dimensions resources, one in <code>res/values/</code> to provide the pre-Android 4.0 custom margins, and one in <code>res/values-v14/</code> to provide no extra padding for Android 4.0 widgets: <p><strong>res/values/dimens.xml</strong>:<br> - <pre><dimen name="widget_margin">15dp</dimen></pre></p> + <pre><dimen name="widget_margin">8dp</dimen></pre></p> <p><strong>res/values-v14/dimens.xml</strong>:<br> <pre><dimen name="widget_margin">0dp</dimen></pre></p> diff --git a/docs/html/guide/topics/renderscript/index.jd b/docs/html/guide/topics/renderscript/index.jd index eb773109e427..148705cfbca1 100644 --- a/docs/html/guide/topics/renderscript/index.jd +++ b/docs/html/guide/topics/renderscript/index.jd @@ -151,10 +151,9 @@ page.title=RenderScript defining two-, three-, or four-vectors.</li> </ul> - <p>The <a href="{@docRoot}guide/topics/renderscript/rs-api/files.html">RenderScript header files</a> - and LLVM front-end libraries are located in the <code>include</code> and - <code>clang-include</code> directories in the - <code><sdk_root>/platforms/android-11/renderscript</code> directory of the Android SDK. The + <p>The RenderScript header files and LLVM front-end libraries are located in the <code>include/</code> and + <code>clang-include/</code> directories in the + <code><sdk_root>/platforms/android-11/renderscript/</code> directory of the Android SDK. The headers are automatically included for you, except for the RenderScript graphics specific header file, which you can include as follows:</p> <pre> diff --git a/docs/html/images/billing_list_form_2.png b/docs/html/images/billing_list_form_2.png Binary files differnew file mode 100755 index 000000000000..d321a2068a09 --- /dev/null +++ b/docs/html/images/billing_list_form_2.png diff --git a/docs/html/images/billing_product_list.png b/docs/html/images/billing_product_list.png Binary files differindex 49a7e796817e..a89f21b53b35 100755 --- a/docs/html/images/billing_product_list.png +++ b/docs/html/images/billing_product_list.png diff --git a/docs/html/resources/dashboard/opengl.jd b/docs/html/resources/dashboard/opengl.jd index 07a0e431a807..4d0abeceac61 100644 --- a/docs/html/resources/dashboard/opengl.jd +++ b/docs/html/resources/dashboard/opengl.jd @@ -57,7 +57,7 @@ ending on the data collection date noted below.</p> <div class="dashboard-panel"> <img alt="" width="400" height="250" -src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A9.8,90.2" /> +src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A10.1,89.9" /> <table> <tr> @@ -66,14 +66,14 @@ src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl= </tr> <tr> <td>1.1</th> -<td>9.8%</td> +<td>10.1%</td> </tr> <tr> <td>2.0</th> -<td>90.2%</td> +<td>89.9%</td> </tr> </table> -<p><em>Data collected during a 7-day period ending on November 3, 2011</em></p> +<p><em>Data collected during a 7-day period ending on December 1, 2011</em></p> </div> diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd index 8041096b21cf..72370bb4ead8 100644 --- a/docs/html/resources/dashboard/platform-versions.jd +++ b/docs/html/resources/dashboard/platform-versions.jd @@ -52,7 +52,7 @@ Android Market within a 14-day period ending on the data collection date noted b <div class="dashboard-panel"> <img alt="" height="250" width="470" -src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.9,1.4,10.7,40.7,0.5,43.9,0.1,0.9,0.9&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2&chco=c4df9b,6fad0c" /> +src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.8,1.3,9.6,35.3,0.5,50.1,0.1,1.1,1.2&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2&chco=c4df9b,6fad0c" /> <table> <tr> @@ -61,21 +61,21 @@ src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.9,1.4,10.7,40 <th>API Level</th> <th>Distribution</th> </tr> -<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td> <td>3</td><td>0.9%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td> <td>4</td><td>1.4%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td> <td>7</td><td>10.7%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td> <td>8</td><td>40.7%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td> <td>3</td><td>0.8%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td> <td>4</td><td>1.3%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td> <td>7</td><td>9.6%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td> <td>8</td><td>35.3%</td></tr> <tr><td><a href="{@docRoot}sdk/android-2.3.html">Android 2.3 -<br/> Android 2.3.2</a></td><td rowspan="2">Gingerbread</td> <td>9</td><td>0.5%</td></tr> <tr><td><a href="{@docRoot}sdk/android-2.3.3.html">Android 2.3.3 -<br/> - Android 2.3.7</a></td><!-- Gingerbread --> <td>10</td><td>43.9%</td></tr> + Android 2.3.7</a></td><!-- Gingerbread --> <td>10</td><td>50.1%</td></tr> <tr><td><a href="{@docRoot}sdk/android-3.0.html">Android 3.0</a></td> <td rowspan="3">Honeycomb</td> <td>11</td><td>0.1%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>0.9%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>0.9%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>1.1%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>1.2%</td></tr> </table> -<p><em>Data collected during a 14-day period ending on November 3, 2011</em></p> +<p><em>Data collected during a 14-day period ending on December 1, 2011</em></p> <!-- <p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p> --> @@ -104,9 +104,9 @@ Android Market within a 14-day period ending on the date indicated on the x-axis <div class="dashboard-panel"> <img alt="" height="250" width="660" style="padding:5px;background:#fff" -src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C07/15%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2011%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.6,99.5,99.4,99.3,99.2,99.0,98.8,98.7,98.5,98.5,98.2,98.1,98.0|97.3,97.5,97.5,97.5,97.7,97.6,97.5,97.5,97.5,97.5,97.1,97.1,97.1|94.3,94.8,95.0,95.2,95.5,95.5,95.5,95.6,95.7,95.8,95.6,95.9,95.7|69.8,71.5,73.9,75.4,77.6,79.0,80.2,81.1,82.4,83.3,83.8,84.9,85.0|4.0,6.1,9.5,13.6,17.8,20.6,24.3,27.5,31.2,34.7,38.3,41.3,44.0|3.0,5.1,8.4,12.6,16.8,20.0,23.7,26.9,30.6,34.1,37.8,40.8,43.5&chm=b,c3df9b,0,1,0|b,b4db77,1,2,0|tAndroid 2.1,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|tAndroid 2.2,3f5e0e,3,0,15,,t::-5|b,96dd28,3,4,0|b,83c916,4,5,0|tAndroid 2.3.3,131d02,5,3,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android 2.2|Android 2.3|Android 2.3.3&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08" /> +src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A|06/01|06/15|07/01|07/15|08/01|08/15|09/01|09/15|10/01|10/15|11/01|11/15|12/01|1%3A|2011||||||||||||2011|2%3A|0%25|25%25|50%25|75%25|100%25|3%3A|0%25|25%25|50%25|75%25|100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.4,99.3,99.2,99.0,98.8,98.7,98.5,98.5,98.2,98.1,98.0,99.9,99.9|97.5,97.5,97.7,97.6,97.5,97.5,97.5,97.5,97.1,97.1,97.0,99.1,99.1|95.0,95.2,95.5,95.5,95.5,95.6,95.7,95.8,95.6,95.9,95.7,97.7,97.8|73.9,75.4,77.6,79.0,80.2,81.1,82.4,83.3,83.8,84.9,85.1,87.5,88.2|9.5,13.6,17.8,20.6,24.3,27.5,31.2,34.7,38.3,41.3,44.0,48.9,52.9|8.4,12.6,16.8,20.0,23.7,26.9,30.6,34.1,37.8,40.8,43.5,48.4,52.4|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,2.0,2.3|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.2&chm=b,c3df9b,0,1,0|b,b8dc82,1,2,0|tAndroid%202.1,608920,2,0,15,,t::-5|b,addb67,2,3,0|tAndroid%202.2,517617,3,0,15,,t::-5|b,a3db4b,3,4,0|b,98dc2e,4,5,0|tAndroid%202.3.3,334d0a,5,1,15,,t::-5|b,8cd41b,5,6,0|b,7ec113,6,7,0|B,6fad0c,7,8,0&chg=7,25&chdl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.1|Android%203.2&chco=add274,a2d15a,97d13e,8bcb28,7dba1e,6ea715,5f920e,507d08" /> -<p><em>Last historical dataset collected during a 14-day period ending on November 3, 2011</em></p> +<p><em>Last historical dataset collected during a 14-day period ending on December 1, 2011</em></p> </div><!-- end dashboard-panel --> diff --git a/docs/html/resources/dashboard/screens.jd b/docs/html/resources/dashboard/screens.jd index ec3034dbcfc2..79d59d91aa7b 100644 --- a/docs/html/resources/dashboard/screens.jd +++ b/docs/html/resources/dashboard/screens.jd @@ -60,7 +60,7 @@ ending on the data collection date noted below.</p> <div class="dashboard-panel"> <img alt="" width="400" height="250" -src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A2.9,0.1,3.1,70.8,1.0,17.7,3.0,1.3" /> +src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A3.1,0.1,3.1,71.0,1.0,17.5,2.9,1.3" /> <table> <tr> @@ -73,13 +73,13 @@ src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl= <tr><th scope="row">small</th> <td>1.3%</td> <!-- small/ldpi --> <td></td> <!-- small/mdpi --> -<td>3.0%</td> <!-- small/hdpi --> +<td>2.9%</td> <!-- small/hdpi --> <td></td> <!-- small/xhdpi --> </tr> <tr><th scope="row">normal</th> <td>1.0%</td> <!-- normal/ldpi --> -<td>17.7%</td> <!-- normal/mdpi --> -<td>70.8%</td> <!-- normal/hdpi --> +<td>17.5%</td> <!-- normal/mdpi --> +<td>71%</td> <!-- normal/hdpi --> <td></td> <!-- normal/xhdpi --> </tr> <tr><th scope="row">large</th> @@ -90,12 +90,12 @@ src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl= </tr> <tr><th scope="row">xlarge</th> <td></td> <!-- xlarge/ldpi --> -<td>2.9%</td> <!-- xlarge/mdpi --> +<td>3.1%</td> <!-- xlarge/mdpi --> <td></td> <!-- xlarge/hdpi --> <td></td> <!-- xlarge/xhdpi --> </tr> </table> -<p><em>Data collected during a 7-day period ending on November 3, 2011</em></p> +<p><em>Data collected during a 7-day period ending on December 1, 2011</em></p> </div> diff --git a/libs/rs/rsScriptC.cpp b/libs/rs/rsScriptC.cpp index 2e7f2134fbaa..cd7b3a72ffef 100644 --- a/libs/rs/rsScriptC.cpp +++ b/libs/rs/rsScriptC.cpp @@ -21,6 +21,7 @@ #ifndef ANDROID_RS_SERIALIZE #include <bcinfo/BitcodeTranslator.h> +#include <bcinfo/BitcodeWrapper.h> #endif using namespace android; @@ -196,7 +197,24 @@ bool ScriptC::runCompiler(Context *rsc, //LOGE("runCompiler %p %p %p %p %p %i", rsc, this, resName, cacheDir, bitcode, bitcodeLen); #ifndef ANDROID_RS_SERIALIZE - uint32_t sdkVersion = rsc->getTargetSdkVersion(); + uint32_t sdkVersion = 0; + bcinfo::BitcodeWrapper bcWrapper((const char *)bitcode, bitcodeLen); + if (!bcWrapper.unwrap()) { + LOGE("Bitcode is not in proper container format (raw or wrapper)"); + return false; + } + + rsAssert(bcWrapper.getHeaderVersion() == 0); + if (bcWrapper.getBCFileType() == bcinfo::BC_WRAPPER) { + sdkVersion = bcWrapper.getTargetAPI(); + } + + if (sdkVersion == 0) { + // This signals that we didn't have a wrapper containing information + // about the bitcode. + sdkVersion = rsc->getTargetSdkVersion(); + } + if (BT) { delete BT; } diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 5e7a7ebd42ed..b9a11cd35eea 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -1576,49 +1576,83 @@ public class AudioService extends IAudioService.Stub { private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = new BluetoothProfile.ServiceListener() { public void onServiceConnected(int profile, BluetoothProfile proxy) { - synchronized (mScoClients) { - // Discard timeout message - mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); - mBluetoothHeadset = (BluetoothHeadset) proxy; - List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices(); + BluetoothDevice btDevice; + List<BluetoothDevice> deviceList; + switch(profile) { + case BluetoothProfile.A2DP: + BluetoothA2dp a2dp = (BluetoothA2dp) proxy; + deviceList = a2dp.getConnectedDevices(); if (deviceList.size() > 0) { - mBluetoothHeadsetDevice = deviceList.get(0); - } else { - mBluetoothHeadsetDevice = null; - } - // Refresh SCO audio state - checkScoAudioState(); - // Continue pending action if any - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) { - boolean status = false; - if (mBluetoothHeadsetDevice != null) { - switch (mScoAudioState) { - case SCO_STATE_ACTIVATE_REQ: - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - status = mBluetoothHeadset.startScoUsingVirtualVoiceCall( - mBluetoothHeadsetDevice); - break; - case SCO_STATE_DEACTIVATE_REQ: - status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( - mBluetoothHeadsetDevice); - break; - case SCO_STATE_DEACTIVATE_EXT_REQ: - status = mBluetoothHeadset.stopVoiceRecognition( - mBluetoothHeadsetDevice); - } + btDevice = deviceList.get(0); + handleA2dpConnectionStateChange(btDevice, a2dp.getConnectionState(btDevice)); + } + break; + + case BluetoothProfile.HEADSET: + synchronized (mScoClients) { + // Discard timeout message + mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); + mBluetoothHeadset = (BluetoothHeadset) proxy; + deviceList = mBluetoothHeadset.getConnectedDevices(); + if (deviceList.size() > 0) { + mBluetoothHeadsetDevice = deviceList.get(0); + } else { + mBluetoothHeadsetDevice = null; } - if (!status) { - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0, - SENDMSG_REPLACE, 0, 0, null, 0); + // Refresh SCO audio state + checkScoAudioState(); + // Continue pending action if any + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ || + mScoAudioState == SCO_STATE_DEACTIVATE_REQ || + mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) { + boolean status = false; + if (mBluetoothHeadsetDevice != null) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVATE_REQ: + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + status = mBluetoothHeadset.startScoUsingVirtualVoiceCall( + mBluetoothHeadsetDevice); + break; + case SCO_STATE_DEACTIVATE_REQ: + status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( + mBluetoothHeadsetDevice); + break; + case SCO_STATE_DEACTIVATE_EXT_REQ: + status = mBluetoothHeadset.stopVoiceRecognition( + mBluetoothHeadsetDevice); + } + } + if (!status) { + sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, 0, + SENDMSG_REPLACE, 0, 0, null, 0); + } } } + break; + + default: + break; } } public void onServiceDisconnected(int profile) { - synchronized (mScoClients) { - mBluetoothHeadset = null; + switch(profile) { + case BluetoothProfile.A2DP: + synchronized (mConnectedDevices) { + if (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)) { + makeA2dpDeviceUnavailableNow( + mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); + } + } + break; + + case BluetoothProfile.HEADSET: + synchronized (mScoClients) { + mBluetoothHeadset = null; + } + break; + + default: + break; } } }; @@ -2197,15 +2231,17 @@ public class AudioService extends IAudioService.Stub { AudioSystem.setParameters("restarting=true"); // Restore device connection states - Set set = mConnectedDevices.entrySet(); - Iterator i = set.iterator(); - while(i.hasNext()){ - Map.Entry device = (Map.Entry)i.next(); - AudioSystem.setDeviceConnectionState(((Integer)device.getKey()).intValue(), - AudioSystem.DEVICE_STATE_AVAILABLE, - (String)device.getValue()); + synchronized (mConnectedDevices) { + Set set = mConnectedDevices.entrySet(); + Iterator i = set.iterator(); + while(i.hasNext()){ + Map.Entry device = (Map.Entry)i.next(); + AudioSystem.setDeviceConnectionState( + ((Integer)device.getKey()).intValue(), + AudioSystem.DEVICE_STATE_AVAILABLE, + (String)device.getValue()); + } } - // Restore call state AudioSystem.setPhoneState(mMode); @@ -2244,7 +2280,9 @@ public class AudioService extends IAudioService.Stub { case MSG_BTA2DP_DOCK_TIMEOUT: // msg.obj == address of BTA2DP device - makeA2dpDeviceUnavailableNow( (String) msg.obj ); + synchronized (mConnectedDevices) { + makeA2dpDeviceUnavailableNow( (String) msg.obj ); + } break; case MSG_SET_FORCE_USE: @@ -2304,6 +2342,7 @@ public class AudioService extends IAudioService.Stub { } } + // must be called synchronized on mConnectedDevices private void makeA2dpDeviceAvailable(String address) { AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, AudioSystem.DEVICE_STATE_AVAILABLE, @@ -2314,6 +2353,7 @@ public class AudioService extends IAudioService.Stub { address); } + // must be called synchronized on mConnectedDevices private void makeA2dpDeviceUnavailableNow(String address) { Intent noisyIntent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); mContext.sendBroadcast(noisyIntent); @@ -2323,6 +2363,7 @@ public class AudioService extends IAudioService.Stub { mConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); } + // must be called synchronized on mConnectedDevices private void makeA2dpDeviceUnavailableLater(String address) { // prevent any activity on the A2DP audio output to avoid unwanted // reconnection of the sink. @@ -2335,14 +2376,60 @@ public class AudioService extends IAudioService.Stub { } + // must be called synchronized on mConnectedDevices private void cancelA2dpDeviceTimeout() { mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT); } + // must be called synchronized on mConnectedDevices private boolean hasScheduledA2dpDockTimeout() { return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT); } + private void handleA2dpConnectionStateChange(BluetoothDevice btDevice, int state) + { + if (btDevice == null) { + return; + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + synchronized (mConnectedDevices) { + boolean isConnected = + (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) && + mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP).equals(address)); + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { + // introduction of a delay for transient disconnections of docks when + // power is rapidly turned off/on, this message will be canceled if + // we reconnect the dock under a preset delay + makeA2dpDeviceUnavailableLater(address); + // the next time isConnected is evaluated, it will be false for the dock + } + } else { + makeA2dpDeviceUnavailableNow(address); + } + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + // this could be a reconnection after a transient disconnection + cancelA2dpDeviceTimeout(); + mDockAddress = address; + } else { + // this could be a connection of another A2DP device before the timeout of + // a dock: cancel the dock timeout, and make the dock unavailable now + if(hasScheduledA2dpDockTimeout()) { + cancelA2dpDeviceTimeout(); + makeA2dpDeviceUnavailableNow(mDockAddress); + } + } + makeA2dpDeviceAvailable(address); + } + } + } + /* cache of the address of the last dock the device was connected to */ private String mDockAddress; @@ -2380,44 +2467,8 @@ public class AudioService extends IAudioService.Stub { int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (btDevice == null) { - return; - } - String address = btDevice.getAddress(); - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - address = ""; - } - boolean isConnected = - (mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) && - mConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP).equals(address)); - - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - if (state == BluetoothProfile.STATE_DISCONNECTED) { - // introduction of a delay for transient disconnections of docks when - // power is rapidly turned off/on, this message will be canceled if - // we reconnect the dock under a preset delay - makeA2dpDeviceUnavailableLater(address); - // the next time isConnected is evaluated, it will be false for the dock - } - } else { - makeA2dpDeviceUnavailableNow(address); - } - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - // this could be a reconnection after a transient disconnection - cancelA2dpDeviceTimeout(); - mDockAddress = address; - } else { - // this could be a connection of another A2DP device before the timeout of - // a dock: cancel the dock timeout, and make the dock unavailable now - if(hasScheduledA2dpDockTimeout()) { - cancelA2dpDeviceTimeout(); - makeA2dpDeviceUnavailableNow(mDockAddress); - } - } - makeA2dpDeviceAvailable(address); - } + + handleA2dpConnectionStateChange(btDevice, state); } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED); @@ -2446,103 +2497,126 @@ public class AudioService extends IAudioService.Stub { if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } - boolean isConnected = (mConnectedDevices.containsKey(device) && - mConnectedDevices.get(device).equals(address)); - synchronized (mScoClients) { - if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { - AudioSystem.setDeviceConnectionState(device, + synchronized (mConnectedDevices) { + boolean isConnected = (mConnectedDevices.containsKey(device) && + mConnectedDevices.get(device).equals(address)); + + synchronized (mScoClients) { + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE, address); - mConnectedDevices.remove(device); - mBluetoothHeadsetDevice = null; - resetBluetoothSco(); - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - AudioSystem.setDeviceConnectionState(device, - AudioSystem.DEVICE_STATE_AVAILABLE, - address); - mConnectedDevices.put(new Integer(device), address); - mBluetoothHeadsetDevice = btDevice; + mConnectedDevices.remove(device); + mBluetoothHeadsetDevice = null; + resetBluetoothSco(); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + AudioSystem.setDeviceConnectionState(device, + AudioSystem.DEVICE_STATE_AVAILABLE, + address); + mConnectedDevices.put(new Integer(device), address); + mBluetoothHeadsetDevice = btDevice; + } } } } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) { int state = intent.getIntExtra("state", 0); int microphone = intent.getIntExtra("microphone", 0); - if (microphone != 0) { - boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADSET); - if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, - AudioSystem.DEVICE_STATE_UNAVAILABLE, - ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADSET); - } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, - AudioSystem.DEVICE_STATE_AVAILABLE, - ""); - mConnectedDevices.put( - new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADSET), ""); + synchronized (mConnectedDevices) { + if (microphone != 0) { + boolean isConnected = + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADSET); + if (state == 0 && isConnected) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADSET); + } else if (state == 1 && !isConnected) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADSET, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); + mConnectedDevices.put( + new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADSET), ""); + } + } else { + boolean isConnected = + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); + if (state == 0 && isConnected) { + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); + } else if (state == 1 && !isConnected) { + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); + mConnectedDevices.put( + new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), ""); + } } - } else { + } + } else if (action.equals(Intent.ACTION_USB_ANLG_HEADSET_PLUG)) { + int state = intent.getIntExtra("state", 0); + Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_ANLG_HEADSET_PLUG, state = "+state); + synchronized (mConnectedDevices) { boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, - AudioSystem.DEVICE_STATE_UNAVAILABLE, - ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE); + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, - AudioSystem.DEVICE_STATE_AVAILABLE, - ""); + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); mConnectedDevices.put( - new Integer(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE), ""); + new Integer(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET), ""); } } - } else if (action.equals(Intent.ACTION_USB_ANLG_HEADSET_PLUG)) { - int state = intent.getIntExtra("state", 0); - Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_ANLG_HEADSET_PLUG, state = "+state); - boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); - if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, - AudioSystem.DEVICE_STATE_UNAVAILABLE, ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET); - } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, - AudioSystem.DEVICE_STATE_AVAILABLE, ""); - mConnectedDevices.put( - new Integer(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET), ""); - } } else if (action.equals(Intent.ACTION_HDMI_AUDIO_PLUG)) { int state = intent.getIntExtra("state", 0); Log.v(TAG, "Broadcast Receiver: Got ACTION_HDMI_AUDIO_PLUG, state = "+state); - boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_AUX_DIGITAL); - if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL, - AudioSystem.DEVICE_STATE_UNAVAILABLE, ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_AUX_DIGITAL); - } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL, - AudioSystem.DEVICE_STATE_AVAILABLE, ""); - mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_AUX_DIGITAL), ""); + synchronized (mConnectedDevices) { + boolean isConnected = + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_AUX_DIGITAL); + if (state == 0 && isConnected) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_AUX_DIGITAL); + } else if (state == 1 && !isConnected) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_AUX_DIGITAL, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); + mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_AUX_DIGITAL), ""); + } } } else if (action.equals(Intent.ACTION_USB_DGTL_HEADSET_PLUG)) { int state = intent.getIntExtra("state", 0); Log.v(TAG, "Broadcast Receiver: Got ACTION_USB_DGTL_HEADSET_PLUG, state = "+state); - boolean isConnected = - mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET); - if (state == 0 && isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, - AudioSystem.DEVICE_STATE_UNAVAILABLE, ""); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET); - } else if (state == 1 && !isConnected) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, - AudioSystem.DEVICE_STATE_AVAILABLE, ""); - mConnectedDevices.put( - new Integer(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET), ""); + synchronized (mConnectedDevices) { + boolean isConnected = + mConnectedDevices.containsKey(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET); + if (state == 0 && isConnected) { + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + ""); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET); + } else if (state == 1 && !isConnected) { + AudioSystem.setDeviceConnectionState( + AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, + AudioSystem.DEVICE_STATE_AVAILABLE, + ""); + mConnectedDevices.put( + new Integer(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET), ""); + } } } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { boolean broadcast = false; @@ -2606,6 +2680,12 @@ public class AudioService extends IAudioService.Stub { newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_DISCONNECTED); mContext.sendStickyBroadcast(newIntent); + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.A2DP); + } } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) { if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { // a package is being removed, not replaced @@ -3407,7 +3487,7 @@ public class AudioService extends IAudioService.Stub { updateRemoteControlDisplay_syncAfRcs(infoChangedFlags); } - /** + /** * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) * precondition: mediaIntent != null, target != null */ @@ -3423,7 +3503,7 @@ public class AudioService extends IAudioService.Stub { } } - /** + /** * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) * precondition: mediaIntent != null, eventReceiver != null */ diff --git a/media/libstagefright/AVIExtractor.cpp b/media/libstagefright/AVIExtractor.cpp index 0be2ca40153d..815f987bb504 100644 --- a/media/libstagefright/AVIExtractor.cpp +++ b/media/libstagefright/AVIExtractor.cpp @@ -1094,7 +1094,7 @@ status_t AVIExtractor::addH264CodecSpecificData(size_t trackIndex) { CHECK(meta->findData(kKeyAVCC, &type, &csd, &csdSize)); track->mMeta->setInt32(kKeyWidth, width); - track->mMeta->setInt32(kKeyHeight, width); + track->mMeta->setInt32(kKeyHeight, height); track->mMeta->setData(kKeyAVCC, type, csd, csdSize); return OK; diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index e937587c62eb..ec08e6c05c39 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -46,26 +46,6 @@ </intent-filter> </receiver> - <!-- should you need to launch the screensaver, this is a good way to do it --> - <activity android:name=".DreamsDockLauncher" - android:theme="@android:style/Theme.Dialog" - android:label="@string/dreams_dock_launcher"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </activity> - - <!-- launch screensaver on (desk) dock event --> - <receiver android:name=".DreamsDockLauncher$DockEventReceiver" - android:exported="true" - > - <intent-filter> - <action android:name="android.intent.action.DOCK_EVENT" /> - </intent-filter> - </receiver> - - <activity android:name=".usb.UsbStorageActivity" android:label="@*android:string/usb_storage_activity_title" android:excludeFromRecents="true"> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index fc81f8ef6dd7..2c1473b22a41 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -362,7 +362,4 @@ <!-- Content description of the clear button in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_clear_all">Clear all notifications.</string> - - <!-- Description of the desk dock action that invokes the Android Dreams screen saver feature --> - <string name="dreams_dock_launcher">Activate screen saver</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/DreamsDockLauncher.java b/packages/SystemUI/src/com/android/systemui/DreamsDockLauncher.java deleted file mode 100644 index 1db2a7feca00..000000000000 --- a/packages/SystemUI/src/com/android/systemui/DreamsDockLauncher.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.android.systemui; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.provider.Settings; -import android.util.Slog; - -public class DreamsDockLauncher extends Activity { - private static final String TAG = "DreamsDockLauncher"; - - // Launch the screen saver if started as an activity. - @Override - protected void onCreate (Bundle icicle) { - super.onCreate(icicle); - launchDream(this); - finish(); - } - - private static void launchDream(Context context) { - try { - String component = Settings.Secure.getString( - context.getContentResolver(), Settings.Secure.SCREENSAVER_COMPONENT); - if (component == null) { - component = context.getResources().getString( - com.android.internal.R.string.config_defaultDreamComponent); - } - if (component != null) { - // dismiss the notification shade, recents, etc. - context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - - ComponentName cn = ComponentName.unflattenFromString(component); - Intent zzz = new Intent(Intent.ACTION_MAIN) - .setComponent(cn) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_NO_USER_ACTION - | Intent.FLAG_FROM_BACKGROUND - | Intent.FLAG_ACTIVITY_NO_HISTORY - ); - Slog.v(TAG, "Starting screen saver on dock event: " + component); - context.startActivity(zzz); - } else { - Slog.e(TAG, "Couldn't start screen saver: none selected"); - } - } catch (android.content.ActivityNotFoundException exc) { - // no screensaver? give up - Slog.e(TAG, "Couldn't start screen saver: none installed"); - } - } - - // Trap low-level dock events and launch the screensaver. - public static class DockEventReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - final boolean activateOnDock = 0 != Settings.Secure.getInt( - context.getContentResolver(), - Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1); - - if (!activateOnDock) return; - - if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) { - Bundle extras = intent.getExtras(); - int state = extras - .getInt(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (state == Intent.EXTRA_DOCK_STATE_DESK - || state == Intent.EXTRA_DOCK_STATE_LE_DESK - || state == Intent.EXTRA_DOCK_STATE_HE_DESK) { - launchDream(context); - } - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 2dcd80d74d1b..2232995c5c64 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -34,6 +34,7 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.PointF; +import android.hardware.CameraSound; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; @@ -49,6 +50,7 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.Interpolator; import android.widget.ImageView; + import com.android.systemui.R; import java.io.File; @@ -254,6 +256,8 @@ class GlobalScreenshot { private float mBgPadding; private float mBgPaddingScale; + private CameraSound mCameraSound; + /** * @param context everything needs a context :( @@ -303,6 +307,9 @@ class GlobalScreenshot { // Scale has to account for both sides of the bg mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding); mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels; + + // Setup the Camera shutter sound + mCameraSound = new CameraSound(); } /** @@ -413,6 +420,9 @@ class GlobalScreenshot { mScreenshotLayout.post(new Runnable() { @Override public void run() { + // Play the shutter sound to notify that we've taken a screenshot + mCameraSound.playSound(CameraSound.SHUTTER_CLICK); + mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotView.buildLayer(); mScreenshotAnimation.start(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 694da2088354..97a18555e432 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Resources; +import android.graphics.Rect; import android.os.ServiceManager; import android.util.AttributeSet; import android.util.Slog; @@ -32,6 +33,10 @@ import android.view.Surface; import android.view.WindowManager; import android.widget.LinearLayout; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.StringBuilder; + import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; @@ -237,4 +242,79 @@ public class NavigationBarView extends LinearLayout { Slog.d(TAG, "reorient(): rot=" + mDisplay.getRotation()); } } + + private String getResourceName(int resId) { + if (resId != 0) { + final android.content.res.Resources res = mContext.getResources(); + try { + return res.getResourceName(resId); + } catch (android.content.res.Resources.NotFoundException ex) { + return "(unknown)"; + } + } else { + return "(null)"; + } + } + + private static String visibilityToString(int vis) { + switch (vis) { + case View.INVISIBLE: + return "INVISIBLE"; + case View.GONE: + return "GONE"; + default: + return "VISIBLE"; + } + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("NavigationBarView {"); + final Rect r = new Rect(); + + pw.println(String.format(" this: " + PhoneStatusBar.viewInfo(this) + + " " + visibilityToString(getVisibility()))); + + getWindowVisibleDisplayFrame(r); + final boolean offscreen = r.right > mDisplay.getRawWidth() + || r.bottom > mDisplay.getRawHeight(); + pw.println(" window: " + + r.toShortString() + + " " + visibilityToString(getWindowVisibility()) + + (offscreen ? " OFFSCREEN!" : "")); + + pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s", + getResourceName(mCurrentView.getId()), + mCurrentView.getWidth(), mCurrentView.getHeight(), + visibilityToString(mCurrentView.getVisibility()))); + + pw.println(String.format(" disabled=0x%08x vertical=%s hidden=%s low=%s menu=%s", + mDisabledFlags, + mVertical ? "true" : "false", + mHidden ? "true" : "false", + mLowProfile ? "true" : "false", + mShowMenu ? "true" : "false")); + + final View back = getBackButton(); + final View home = getHomeButton(); + final View recent = getRecentsButton(); + final View menu = getMenuButton(); + + pw.println(" back: " + + PhoneStatusBar.viewInfo(back) + + " " + visibilityToString(back.getVisibility()) + ); + pw.println(" home: " + + PhoneStatusBar.viewInfo(home) + + " " + visibilityToString(home.getVisibility()) + ); + pw.println(" rcnt: " + + PhoneStatusBar.viewInfo(recent) + + " " + visibilityToString(recent.getVisibility()) + ); + pw.println(" menu: " + + PhoneStatusBar.viewInfo(menu) + + " " + visibilityToString(menu.getVisibility()) + ); + pw.println(" }"); + } } 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 f0093d33040a..c69a145dd28c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -1735,9 +1735,9 @@ public class PhoneStatusBar extends StatusBar { return anim; } - public String viewInfo(View v) { - return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() - + " " + v.getWidth() + "x" + v.getHeight() + ")"; + public static String viewInfo(View v) { + return "[(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() + + ") " + v.getWidth() + "x" + v.getHeight() + "]"; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -1768,6 +1768,13 @@ public class PhoneStatusBar extends StatusBar { + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY()); } + pw.print(" mNavigationBarView="); + if (mNavigationBarView == null) { + pw.println("null"); + } else { + mNavigationBarView.dump(fd, pw, args); + } + if (DUMPTRUCK) { synchronized (mNotificationData) { int N = mNotificationData.size(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java index 7fbf7344a25b..2d951c25f58b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CompatModeButton.java @@ -49,6 +49,10 @@ public class CompatModeButton extends ImageView { public void refresh() { int mode = mAM.getFrontActivityScreenCompatMode(); + if (mode == ActivityManager.COMPAT_MODE_UNKNOWN) { + // If in an unknown state, don't change. + return; + } final boolean vis = (mode != ActivityManager.COMPAT_MODE_NEVER && mode != ActivityManager.COMPAT_MODE_ALWAYS); if (DEBUG) Slog.d(TAG, "compat mode is " + mode + "; icon will " + (vis ? "show" : "hide")); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java index 01406bc2bd3e..757ce0c5fd74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -534,6 +534,7 @@ public class TabletStatusBar extends StatusBar implements mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton); mCompatModeButton.setOnClickListener(mOnClickListener); + mCompatModeButton.setVisibility(View.GONE); // for redirecting errant bar taps to the IME mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar); diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java index f040e87b7bae..8052c8027859 100644 --- a/policy/src/com/android/internal/policy/impl/GlobalActions.java +++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java @@ -121,8 +121,8 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac R.string.global_action_silent_mode_off_status) { void willCreate() { - mEnabledIconResId = (Settings.System.getInt(mContext.getContentResolver(), - Settings.System.VIBRATE_IN_SILENT, 1) == 1) + mEnabledIconResId = + mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE ? R.drawable.ic_audio_ring_notif_vibrate : R.drawable.ic_audio_vol_mute; } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 0655624b2e30..f6bf213df453 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -40,7 +40,6 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.os.BatteryManager; -import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -72,6 +71,7 @@ import android.util.Slog; import android.util.SparseArray; import android.view.Gravity; import android.view.HapticFeedbackConstants; +import android.view.IApplicationToken; import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputDevice; @@ -164,9 +164,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final boolean ENABLE_CAR_DOCK_HOME_CAPTURE = true; static final boolean ENABLE_DESK_DOCK_HOME_CAPTURE = false; - // Should screen savers use their own timeout, or the SCREEN_OFF_TIMEOUT? - static final boolean SEPARATE_TIMEOUT_FOR_SCREEN_SAVER = false; - static final int LONG_PRESS_POWER_NOTHING = 0; static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; static final int LONG_PRESS_POWER_SHUT_OFF = 2; @@ -343,6 +340,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // The last window we were told about in focusChanged. WindowState mFocusedWindow; + IApplicationToken mFocusedApp; private final InputHandler mPointerLocationInputHandler = new BaseInputHandler() { @Override @@ -422,12 +420,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mLockScreenTimeout; boolean mLockScreenTimerActive; - // visual screen saver support - int mScreenSaverTimeout = 0; - boolean mScreenSaverEnabledByUser = false; - boolean mScreenSaverMayRun = true; // false if a wakelock is held - boolean mPluggedIn; - // Behavior of ENDCALL Button. (See Settings.System.END_BUTTON_BEHAVIOR.) int mEndcallBehavior; @@ -490,12 +482,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { Settings.Secure.DEFAULT_INPUT_METHOD), false, this); resolver.registerContentObserver(Settings.System.getUriFor( "fancy_rotation_anim"), false, this); - resolver.registerContentObserver(Settings.Secure.getUriFor( - Settings.Secure.SCREENSAVER_ENABLED), false, this); - if (SEPARATE_TIMEOUT_FOR_SCREEN_SAVER) { - resolver.registerContentObserver(Settings.Secure.getUriFor( - "screensaver_timeout"), false, this); - } // otherwise SCREEN_OFF_TIMEOUT will do nicely updateSettings(); } @@ -803,14 +789,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { Intent.EXTRA_DOCK_STATE_UNDOCKED); } - // watch the plug to know whether to trigger the screen saver - filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - intent = context.registerReceiver(mPowerReceiver, filter); - if (intent != null) { - mPluggedIn = (0 != intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)); - } - mVibrator = new Vibrator(); mLongPressVibePattern = getLongIntArray(mContext.getResources(), com.android.internal.R.array.config_longPressVibePattern); @@ -959,23 +937,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHasSoftInput = hasSoftInput; updateRotation = true; } - - mScreenSaverEnabledByUser = 0 != Settings.Secure.getInt(resolver, - Settings.Secure.SCREENSAVER_ENABLED, 1); - - if (SEPARATE_TIMEOUT_FOR_SCREEN_SAVER) { - mScreenSaverTimeout = Settings.Secure.getInt(resolver, - "screensaver_timeout", 0); - } else { - mScreenSaverTimeout = Settings.System.getInt(resolver, - Settings.System.SCREEN_OFF_TIMEOUT, 0); - if (mScreenSaverTimeout > 0) { - // We actually want to activate the screensaver just before the - // power manager's screen timeout - mScreenSaverTimeout -= 5000; - } - } - updateScreenSaverTimeoutLocked(); } if (updateRotation) { updateRotation(true); @@ -3027,15 +2988,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }; - BroadcastReceiver mPowerReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { - mPluggedIn = (0 != intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)); - if (localLOGV) Log.v(TAG, "BATTERY_CHANGED: " + intent + " plugged=" + mPluggedIn); - } - } - }; - /** {@inheritDoc} */ public void screenTurnedOff(int why) { EventLog.writeEvent(70000, 0); @@ -3047,7 +2999,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { synchronized (mLock) { updateOrientationListenerLp(); updateLockScreenTimeout(); - updateScreenSaverTimeoutLocked(); } } @@ -3094,7 +3045,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenOnEarly = true; updateOrientationListenerLp(); updateLockScreenTimeout(); - updateScreenSaverTimeoutLocked(); } } @@ -3489,85 +3439,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout); } } - - synchronized (mLock) { - // Only posts messages; holds no additional locks. - updateScreenSaverTimeoutLocked(); - } - } - - Runnable mScreenSaverActivator = new Runnable() { - public void run() { - if (!(mScreenSaverMayRun && mScreenOnEarly)) { - Log.w(TAG, "mScreenSaverActivator ran, but the screensaver should not be showing. Who's driving this thing?"); - return; - } - if (!mPluggedIn) { - if (localLOGV) Log.v(TAG, "mScreenSaverActivator: not running screen saver when not plugged in"); - return; - } - // Quick fix for automation tests. - // The correct fix is to move this triggering logic to PowerManager, where more complete - // information about wakelocks (including StayOnWhilePluggedIn) is available. - if (Settings.System.getInt(mContext.getContentResolver(), - Settings.System.STAY_ON_WHILE_PLUGGED_IN, - BatteryManager.BATTERY_PLUGGED_AC) != 0) { - Log.v(TAG, "mScreenSaverActivator: not running screen saver when STAY_ON_WHILE_PLUGGED_IN"); - return; - } - - if (localLOGV) Log.v(TAG, "mScreenSaverActivator entering dreamland"); - - try { - String component = Settings.Secure.getString( - mContext.getContentResolver(), Settings.Secure.SCREENSAVER_COMPONENT); - if (component == null) { - component = mContext.getResources().getString(R.string.config_defaultDreamComponent); - } - if (component != null) { - // dismiss the notification shade, recents, etc. - mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) - .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)); - - ComponentName cn = ComponentName.unflattenFromString(component); - Intent intent = new Intent(Intent.ACTION_MAIN) - .setComponent(cn) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_NO_USER_ACTION - | Intent.FLAG_FROM_BACKGROUND - | Intent.FLAG_ACTIVITY_NO_HISTORY - ); - mContext.startActivity(intent); - } else { - Log.e(TAG, "Couldn't start screen saver: none selected"); - } - } catch (android.content.ActivityNotFoundException exc) { - // no screensaver? give up - Log.e(TAG, "Couldn't start screen saver: none installed"); - } - } - }; - - // Must call while holding mLock - private void updateScreenSaverTimeoutLocked() { - if (mScreenSaverActivator == null) return; - - mHandler.removeCallbacks(mScreenSaverActivator); - if (mScreenSaverEnabledByUser && mScreenSaverMayRun && mScreenOnEarly && mScreenSaverTimeout > 0) { - if (localLOGV) - Log.v(TAG, "scheduling screensaver for " + mScreenSaverTimeout + "ms from now"); - mHandler.postDelayed(mScreenSaverActivator, mScreenSaverTimeout); - } else { - if (localLOGV) { - if (!mScreenSaverEnabledByUser || mScreenSaverTimeout == 0) - Log.v(TAG, "screen saver disabled by user"); - else if (!mScreenOnEarly) - Log.v(TAG, "screen saver disabled while screen off"); - else - Log.v(TAG, "screen saver disabled by wakelock"); - } - } } Runnable mScreenLockTimeout = new Runnable() { @@ -3773,11 +3644,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } public void screenOnStartedLw() { - // The window manager has just grabbed a wake lock. This is our cue to disable the screen - // saver. - synchronized (mLock) { - mScreenSaverMayRun = false; - } } public void screenOnStoppedLw() { @@ -3786,13 +3652,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { long curTime = SystemClock.uptimeMillis(); mPowerManager.userActivity(curTime, false, LocalPowerManager.OTHER_EVENT); } - - synchronized (mLock) { - // even if the keyguard is up, now that all the wakelocks have been released, we - // should re-enable the screen saver - mScreenSaverMayRun = true; - updateScreenSaverTimeoutLocked(); - } } } @@ -3813,11 +3672,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { int diff = visibility ^ mLastSystemUiFlags; final boolean needsMenu = (mFocusedWindow.getAttrs().flags & WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0; - if (diff == 0 && mLastFocusNeedsMenu == needsMenu) { + if (diff == 0 && mLastFocusNeedsMenu == needsMenu + && mFocusedApp == mFocusedWindow.getAppToken()) { return 0; } mLastSystemUiFlags = visibility; mLastFocusNeedsMenu = needsMenu; + mFocusedApp = mFocusedWindow.getAppToken(); mHandler.post(new Runnable() { public void run() { if (mStatusBarService == null) { diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index d34087f959d0..16eeb7babb35 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -163,7 +163,6 @@ public class DeviceStorageMonitorService extends Binder { } catch (IllegalArgumentException e) { // ignore; report -1 } - mCacheFileStats.restat(CACHE_PATH); EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT, mFreeMem, mFreeSystem, mFreeCache); } diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java index d04b4404ae40..8384ebc23d7d 100644 --- a/services/java/com/android/server/TextServicesManagerService.java +++ b/services/java/com/android/server/TextServicesManagerService.java @@ -392,9 +392,16 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { Slog.d(TAG, "FinishSpellCheckerService"); } synchronized(mSpellCheckerMap) { + final ArrayList<SpellCheckerBindGroup> removeList = + new ArrayList<SpellCheckerBindGroup>(); for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) { if (group == null) continue; - group.removeListener(listener); + // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop. + removeList.add(group); + } + final int removeSize = removeList.size(); + for (int i = 0; i < removeSize; ++i) { + removeList.get(i).removeListener(listener); } } } @@ -669,6 +676,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } } + // cleanLocked may remove elements from mSpellCheckerBindGroups private void cleanLocked() { if (DBG) { Slog.d(TAG, "cleanLocked"); diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index e6392d746daa..c7fbc000a3a6 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -65,7 +65,7 @@ class UiModeManagerService extends IUiModeManager.Stub { // Enable launching of applications when entering the dock. private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true; - private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = false; + private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true; private static final int MSG_UPDATE_TWILIGHT = 0; private static final int MSG_ENABLE_LOCATION_UPDATES = 1; diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index fd528cce8490..b70ed9699a0f 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -115,8 +115,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>(); - private final SparseArray<IAccessibilityInteractionConnection> mWindowIdToInteractionConnectionMap = - new SparseArray<IAccessibilityInteractionConnection>(); + private final SparseArray<AccessibilityConnectionWrapper> mWindowIdToInteractionConnectionWrapperMap = + new SparseArray<AccessibilityConnectionWrapper>(); private final SparseArray<IBinder> mWindowIdToWindowTokenMap = new SparseArray<IBinder>(); @@ -439,16 +439,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final IWindow addedWindowToken = windowToken; final IAccessibilityInteractionConnection addedConnection = connection; final int windowId = sNextWindowId++; - addedConnection.asBinder().linkToDeath(new DeathRecipient() { - public void binderDied() { - synchronized (mLock) { - addedConnection.asBinder().unlinkToDeath(this, 0); - removeAccessibilityInteractionConnection(addedWindowToken); - } - } - }, 0); + AccessibilityConnectionWrapper wrapper = new AccessibilityConnectionWrapper(windowId, + connection); + wrapper.linkToDeath(); mWindowIdToWindowTokenMap.put(windowId, addedWindowToken.asBinder()); - mWindowIdToInteractionConnectionMap.put(windowId, connection); + mWindowIdToInteractionConnectionWrapperMap.put(windowId, wrapper); if (DEBUG) { Slog.i(LOG_TAG, "Adding interaction connection to windowId: " + windowId); } @@ -462,18 +457,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub for (int i = 0; i < count; i++) { if (mWindowIdToWindowTokenMap.valueAt(i) == windowToken.asBinder()) { final int windowId = mWindowIdToWindowTokenMap.keyAt(i); - mWindowIdToWindowTokenMap.remove(windowId); - mWindowIdToInteractionConnectionMap.remove(windowId); - if (DEBUG) { - Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId); - } + AccessibilityConnectionWrapper wrapper = + mWindowIdToInteractionConnectionWrapperMap.get(windowId); + wrapper.unlinkToDeath(); + removeAccessibilityInteractionConnectionLocked(windowId); return; } } } } - public IAccessibilityServiceConnection registerEventListener(IEventListener listener) { + public void registerEventListener(IEventListener listener) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, FUNCTION_REGISTER_EVENT_LISTENER); ComponentName componentName = new ComponentName("foo.bar", @@ -501,7 +495,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; Service service = new Service(componentName, accessibilityServiceInfo, true); service.onServiceConnected(componentName, listener.asBinder()); - return service; + } + + /** + * Removes an AccessibilityInteractionConnection. + * + * @param windowId The id of the window to which the connection is targeted. + */ + private void removeAccessibilityInteractionConnectionLocked(int windowId) { + mWindowIdToWindowTokenMap.remove(windowId); + mWindowIdToInteractionConnectionWrapperMap.remove(windowId); + if (DEBUG) { + Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId); + } } /** @@ -594,6 +600,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ private void notifyEventListenerLocked(Service service, int eventType) { IEventListener listener = service.mServiceInterface; + + // If the service died/was disabled while the message for dispatching + // the accessibility event was propagating the listener may be null. + if (listener == null) { + return; + } + AccessibilityEvent event = service.mPendingEvents.get(eventType); // Check for null here because there is a concurrent scenario in which this @@ -618,7 +631,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub service.mPendingEvents.remove(eventType); try { if (mSecurityPolicy.canRetrieveWindowContent(service)) { - event.setConnection(service); + event.setConnectionId(service.mId); } else { event.setSource(null); } @@ -666,6 +679,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mComponentNameToServiceMap.remove(service.mComponentName); mHandler.removeMessages(service.mId); service.unlinkToOwnDeath(); + service.dispose(); updateInputFilterLocked(); return removed; } @@ -895,6 +909,33 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub sendStateToClientsLocked(); } + private class AccessibilityConnectionWrapper implements DeathRecipient { + private final int mWindowId; + private final IAccessibilityInteractionConnection mConnection; + + public AccessibilityConnectionWrapper(int windowId, + IAccessibilityInteractionConnection connection) { + mWindowId = windowId; + mConnection = connection; + } + + public void linkToDeath() throws RemoteException { + mConnection.asBinder().linkToDeath(this, 0); + } + + public void unlinkToDeath() { + mConnection.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + unlinkToDeath(); + synchronized (mLock) { + removeAccessibilityInteractionConnectionLocked(mWindowId); + } + } + } + /** * This class represents an accessibility service. It stores all per service * data required for the service management, provides API for starting/stopping the @@ -997,7 +1038,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!mIsAutomation) { mContext.unbindService(this); } - mService = null; return true; } return false; @@ -1021,7 +1061,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mService = service; mServiceInterface = IEventListener.Stub.asInterface(service); try { - mServiceInterface.setConnection(this); + mServiceInterface.setConnection(this, mId); synchronized (mLock) { tryAddServiceLocked(this); } @@ -1123,14 +1163,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!permissionGranted) { return 0; } else { - connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId); - if (connection == null) { + AccessibilityConnectionWrapper wrapper = + mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId); + if (wrapper == null) { if (DEBUG) { Slog.e(LOG_TAG, "No interaction connection to window: " + accessibilityWindowId); } return 0; } + connection = wrapper.mConnection; } } final int interrogatingPid = Binder.getCallingPid(); @@ -1159,14 +1201,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!permissionGranted) { return false; } else { - connection = mWindowIdToInteractionConnectionMap.get(accessibilityWindowId); - if (connection == null) { + AccessibilityConnectionWrapper wrapper = + mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId); + if (wrapper == null) { if (DEBUG) { Slog.e(LOG_TAG, "No interaction connection to window: " + accessibilityWindowId); } return false; } + connection = wrapper.mConnection; } } final int interrogatingPid = Binder.getCallingPid(); @@ -1197,9 +1241,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mService.unlinkToDeath(this, 0); } + public void dispose() { + try { + // Clear the proxy in the other process so this + // IAccessibilityServiceConnection can be garbage collected. + mServiceInterface.setConnection(null, mId); + } catch (RemoteException re) { + /* ignore */ + } + mService = null; + mServiceInterface = null; + } + public void binderDied() { synchronized (mLock) { - mService.unlinkToDeath(this, 0); + unlinkToOwnDeath(); tryRemoveServiceLocked(this); // We no longer have an automation service, so restore // the state based on values in the settings database. @@ -1214,7 +1270,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (DEBUG) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); } - return mWindowIdToInteractionConnectionMap.get(windowId); + AccessibilityConnectionWrapper wrapper = + mWindowIdToInteractionConnectionWrapperMap.get(windowId); + return (wrapper != null) ? wrapper.mConnection : null; } private float getCompatibilityScale(int windowId) { diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index b36ba3a3cc92..a4d321dadb1e 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -414,7 +414,12 @@ public final class ActivityManagerService extends ActivityManagerNative * is in a different process from the one they are currently in. */ ProcessRecord mPreviousProcess; - + + /** + * The time at which the previous process was last visible. + */ + long mPreviousProcessVisibleTime; + /** * Packages that the user has asked to have run in screen size * compatibility mode instead of filling the screen. @@ -8361,6 +8366,12 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(); pw.println(" mHomeProcess: " + mHomeProcess); pw.println(" mPreviousProcess: " + mPreviousProcess); + if (dumpAll) { + StringBuilder sb = new StringBuilder(128); + sb.append(" mPreviousProcessVisibleTime: "); + TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb); + pw.println(sb); + } if (mHeavyWeightProcess != null) { pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess); } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index 951a946e06a0..c819114f3866 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -80,6 +80,7 @@ final class ActivityRecord { ThumbnailHolder thumbHolder; // where our thumbnails should go. long launchTime; // when we starting launching this activity long startTime; // last time this activity was started + long lastVisibleTime; // last time this activity became visible long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity Configuration configuration; // configuration activity was last running in CompatibilityInfo compat;// last used compatibility mode @@ -188,6 +189,10 @@ final class ActivityRecord { TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime="); TimeUtils.formatDuration(startTime, pw); pw.println(""); } + if (lastVisibleTime != 0) { + pw.print(prefix); pw.print("lastVisibleTime="); + TimeUtils.formatDuration(lastVisibleTime, pw); pw.println(""); + } if (waitingVisible || nowVisible) { pw.print(prefix); pw.print("waitingVisible="); pw.print(waitingVisible); pw.print(" nowVisible="); pw.println(nowVisible); @@ -632,6 +637,7 @@ final class ActivityRecord { ActivityManagerService.TAG, "windowsVisible(): " + this); if (!nowVisible) { nowVisible = true; + lastVisibleTime = SystemClock.uptimeMillis(); if (!idle) { // Instead of doing the full stop routine here, let's just // hide any activities we now can, and let them stop when diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index c7ce3c3eed8d..b5edc0a656cb 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -949,6 +949,22 @@ final class ActivityStack { if (r.configDestroy) { destroyActivityLocked(r, true, false, "stop-config"); resumeTopActivityLocked(null); + } else { + // Now that this process has stopped, we may want to consider + // it to be the previous app to try to keep around in case + // the user wants to return to it. + ProcessRecord fgApp = null; + if (mResumedActivity != null) { + fgApp = mResumedActivity.app; + } else if (mPausingActivity != null) { + fgApp = mPausingActivity.app; + } + if (r.app != null && fgApp != null && r.app != fgApp + && r.lastVisibleTime > mService.mPreviousProcessVisibleTime + && r.app != mService.mHomeProcess) { + mService.mPreviousProcess = r.app; + mService.mPreviousProcessVisibleTime = r.lastVisibleTime; + } } } } @@ -1363,14 +1379,6 @@ final class ActivityStack { + ", nowVisible=" + next.nowVisible); } } - - if (!prev.finishing && prev.app != null && prev.app != next.app - && prev.app != mService.mHomeProcess) { - // We are switching to a new activity that is in a different - // process than the previous one. Note the previous process, - // so we can try to keep it around. - mService.mPreviousProcess = prev.app; - } } // Launching this app's activity, make sure the app is no longer diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java index 636646eef1d1..461989927d44 100644 --- a/telephony/java/com/android/internal/telephony/DataConnection.java +++ b/telephony/java/com/android/internal/telephony/DataConnection.java @@ -699,8 +699,10 @@ public abstract class DataConnection extends StateMachine { break; case EVENT_DISCONNECT: - if (DBG) log("DcDefaultState: msg.what=EVENT_DISCONNECT"); - notifyDisconnectCompleted((DisconnectParams) msg.obj); + if (DBG) { + log("DcDefaultState deferring msg.what=EVENT_DISCONNECT" + mRefCount); + } + deferMessage(msg); break; case EVENT_RIL_CONNECTED: @@ -807,6 +809,12 @@ public abstract class DataConnection extends StateMachine { retVal = HANDLED; break; + case EVENT_DISCONNECT: + if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT"); + notifyDisconnectCompleted((DisconnectParams)msg.obj); + retVal = HANDLED; + break; + default: if (VDBG) { log("DcInactiveState nothandled msg.what=0x" + @@ -831,13 +839,6 @@ public abstract class DataConnection extends StateMachine { ConnectionParams cp; switch (msg.what) { - case EVENT_DISCONNECT: - if (DBG) log("DcActivatingState deferring msg.what=EVENT_DISCONNECT" - + mRefCount); - deferMessage(msg); - retVal = HANDLED; - break; - case EVENT_CONNECT: if (DBG) log("DcActivatingState deferring msg.what=EVENT_CONNECT refCount = " + mRefCount); |