Merge changes I1302cacd,I05125c79,I8d9653ce
* changes:
MTP: Fix some warnings
MTP: Include current property value in GetDevicePropDesc
MTP: Fix reading and writing device property descriptors
diff --git a/api/current.xml b/api/current.xml
index 80428bd..42787e6 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -947,6 +947,17 @@
visibility="public"
>
</field>
+<field name="SET_ALARM"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""com.android.alarm.permission.SET_ALARM""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SET_ALWAYS_FINISH"
type="java.lang.String"
transient="false"
@@ -105949,6 +105960,17 @@
visibility="public"
>
</field>
+<field name="ACTION_VIEW_DOWNLOADS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.intent.action.VIEW_DOWNLOADS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="COLUMN_BYTES_DOWNLOADED_SO_FAR"
type="java.lang.String"
transient="false"
@@ -143318,6 +143340,67 @@
</package>
<package name="android.provider"
>
+<class name="AlarmClock"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="AlarmClock"
+ type="android.provider.AlarmClock"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<field name="ACTION_SET_ALARM"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.intent.action.SET_ALARM""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_HOUR"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.intent.extra.alarm.HOUR""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_MESSAGE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.intent.extra.alarm.MESSAGE""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_MINUTES"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.intent.extra.alarm.MINUTES""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<interface name="BaseColumns"
abstract="true"
static="false"
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 4dee350..b073004 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -35,9 +35,7 @@
import android.view.IWindowManager;
import java.io.BufferedReader;
-import java.io.DataInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -482,6 +480,8 @@
}
class MyActivityController extends IActivityController.Stub {
+ final String mGdbPort;
+
static final int STATE_NORMAL = 0;
static final int STATE_CRASHED = 1;
static final int STATE_EARLY_ANR = 2;
@@ -503,6 +503,14 @@
int mResult;
+ Process mGdbProcess;
+ Thread mGdbThread;
+ boolean mGotGdbPrint;
+
+ MyActivityController(String gdbPort) {
+ mGdbPort = gdbPort;
+ }
+
@Override
public boolean activityResuming(String pkg) throws RemoteException {
synchronized (this) {
@@ -532,7 +540,7 @@
System.out.println("stack:");
System.out.print(stackTrace);
System.out.println("#");
- int result = waitControllerLocked(STATE_CRASHED);
+ int result = waitControllerLocked(pid, STATE_CRASHED);
return result == RESULT_CRASH_KILL ? false : true;
}
}
@@ -545,7 +553,7 @@
System.out.println("processName: " + processName);
System.out.println("processPid: " + pid);
System.out.println("annotation: " + annotation);
- int result = waitControllerLocked(STATE_EARLY_ANR);
+ int result = waitControllerLocked(pid, STATE_EARLY_ANR);
if (result == RESULT_EARLY_ANR_KILL) return -1;
return 0;
}
@@ -561,14 +569,83 @@
System.out.println("processStats:");
System.out.print(processStats);
System.out.println("#");
- int result = waitControllerLocked(STATE_ANR);
+ int result = waitControllerLocked(pid, STATE_ANR);
if (result == RESULT_ANR_KILL) return -1;
if (result == RESULT_ANR_WAIT) return 1;
return 0;
}
}
- int waitControllerLocked(int state) {
+ void killGdbLocked() {
+ mGotGdbPrint = false;
+ if (mGdbProcess != null) {
+ System.out.println("Stopping gdbserver");
+ mGdbProcess.destroy();
+ mGdbProcess = null;
+ }
+ if (mGdbThread != null) {
+ mGdbThread.interrupt();
+ mGdbThread = null;
+ }
+ }
+
+ int waitControllerLocked(int pid, int state) {
+ if (mGdbPort != null) {
+ killGdbLocked();
+
+ try {
+ System.out.println("Starting gdbserver on port " + mGdbPort);
+ System.out.println("Do the following:");
+ System.out.println(" adb forward tcp:" + mGdbPort + " tcp:" + mGdbPort);
+ System.out.println(" gdbclient app_process :" + mGdbPort);
+
+ mGdbProcess = Runtime.getRuntime().exec(new String[] {
+ "gdbserver", ":" + mGdbPort, "--attach", Integer.toString(pid)
+ });
+ final InputStreamReader converter = new InputStreamReader(
+ mGdbProcess.getInputStream());
+ mGdbThread = new Thread() {
+ @Override
+ public void run() {
+ BufferedReader in = new BufferedReader(converter);
+ String line;
+ int count = 0;
+ while (true) {
+ synchronized (MyActivityController.this) {
+ if (mGdbThread == null) {
+ return;
+ }
+ if (count == 2) {
+ mGotGdbPrint = true;
+ MyActivityController.this.notifyAll();
+ }
+ }
+ try {
+ line = in.readLine();
+ if (line == null) {
+ return;
+ }
+ System.out.println("GDB: " + line);
+ count++;
+ } catch (IOException e) {
+ return;
+ }
+ }
+ }
+ };
+ mGdbThread.start();
+
+ // Stupid waiting for .5s. Doesn't matter if we end early.
+ try {
+ this.wait(500);
+ } catch (InterruptedException e) {
+ }
+
+ } catch (IOException e) {
+ System.err.println("Failure starting gdbserver: " + e);
+ killGdbLocked();
+ }
+ }
mState = state;
System.out.println("");
printMessageForState();
@@ -580,6 +657,8 @@
}
}
+ killGdbLocked();
+
return mResult;
}
@@ -681,7 +760,19 @@
}
private void runMonitor() throws Exception {
- MyActivityController controller = new MyActivityController();
+ String opt;
+ String gdbPort = null;
+ while ((opt=nextOption()) != null) {
+ if (opt.equals("--gdb")) {
+ gdbPort = nextArgRequired();
+ } else {
+ System.err.println("Error: Unknown option: " + opt);
+ showUsage();
+ return;
+ }
+ }
+
+ MyActivityController controller = new MyActivityController(gdbPort);
controller.run();
}
@@ -862,7 +953,8 @@
" dump heap: am dumpheap [flags] <PROCESS> <FILE>\n" +
" -n: dump native heap instead of managed heap\n" +
"\n" +
- " start monitoring: am monitor\n" +
+ " start monitoring: am monitor [--gdb <port>]\n" +
+ " --gdb: start gdbserv on the given port at crash/ANR\n" +
"\n" +
" <INTENT> specifications include these flags:\n" +
" [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index a09666e..67bd9f7 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -135,11 +135,11 @@
run_command("PROCESSES AND THREADS", 10, "ps", "-t", "-p", "-P", NULL);
run_command("LIBRANK", 10, "librank", NULL);
- dump_file("BINDER FAILED TRANSACTION LOG", "/proc/binder/failed_transaction_log");
- dump_file("BINDER TRANSACTION LOG", "/proc/binder/transaction_log");
- dump_file("BINDER TRANSACTIONS", "/proc/binder/transactions");
- dump_file("BINDER STATS", "/proc/binder/stats");
- run_command("BINDER PROCESS STATE", 10, "sh", "-c", "cat /proc/binder/proc/*");
+ dump_file("BINDER FAILED TRANSACTION LOG", "/sys/kernel/debug/binder/failed_transaction_log");
+ dump_file("BINDER TRANSACTION LOG", "/sys/kernel/debug/binder/transaction_log");
+ dump_file("BINDER TRANSACTIONS", "/sys/kernel/debug/binder/transactions");
+ dump_file("BINDER STATS", "/sys/kernel/debug/binder/stats");
+ dump_file("BINDER STATE", "/sys/kernel/debug/binder/state");
run_command("FILESYSTEMS & FREE SPACE", 10, "df", NULL);
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index eb4b733..8b54871 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -31,6 +31,8 @@
#include <media/IMediaPlayerService.h>
#include <media/stagefright/foundation/ALooper.h>
#include "include/ARTSPController.h"
+#include "include/LiveSource.h"
+#include "include/NuCachedSource2.h"
#include <media/stagefright/AudioPlayer.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/JPEGSource.h>
@@ -285,19 +287,34 @@
MediaBuffer **buffer, const ReadOptions *options);
private:
+ enum StreamType {
+ AVC,
+ MPEG4,
+ H263,
+ OTHER,
+ };
+
sp<MediaSource> mSource;
- bool mIsAVC;
+ StreamType mStreamType;
DISALLOW_EVIL_CONSTRUCTORS(DetectSyncSource);
};
DetectSyncSource::DetectSyncSource(const sp<MediaSource> &source)
: mSource(source),
- mIsAVC(false) {
+ mStreamType(OTHER) {
const char *mime;
CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
- mIsAVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
+ mStreamType = AVC;
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4)) {
+ mStreamType = MPEG4;
+ CHECK(!"sync frame detection not implemented yet for MPEG4");
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_H263)) {
+ mStreamType = H263;
+ CHECK(!"sync frame detection not implemented yet for H.263");
+ }
}
status_t DetectSyncSource::start(MetaData *params) {
@@ -336,10 +353,10 @@
return err;
}
- if (mIsAVC && isIDRFrame(*buffer)) {
+ if (mStreamType == AVC && isIDRFrame(*buffer)) {
(*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true);
} else {
- (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, false);
+ (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true);
}
return OK;
@@ -347,19 +364,21 @@
////////////////////////////////////////////////////////////////////////////////
-static void writeSourceToMP4(
- sp<MediaSource> &source, bool syncInfoPresent) {
- if (!syncInfoPresent) {
- source = new DetectSyncSource(source);
- }
-
+static void writeSourcesToMP4(
+ Vector<sp<MediaSource> > &sources, bool syncInfoPresent) {
sp<MPEG4Writer> writer =
new MPEG4Writer(gWriteMP4Filename.string());
// at most one minute.
writer->setMaxFileDuration(60000000ll);
- CHECK_EQ(writer->addSource(source), (status_t)OK);
+ for (size_t i = 0; i < sources.size(); ++i) {
+ sp<MediaSource> source = sources.editItemAt(i);
+
+ CHECK_EQ(writer->addSource(
+ syncInfoPresent ? source : new DetectSyncSource(source)),
+ (status_t)OK);
+ }
sp<MetaData> params = new MetaData;
params->setInt32(kKeyNotRealTime, true);
@@ -674,8 +693,10 @@
sp<DataSource> dataSource = DataSource::CreateFromURI(filename);
- if ((strncasecmp(filename, "sine:", 5)
- && strncasecmp(filename, "rtsp://", 7)) && dataSource == NULL) {
+ if (strncasecmp(filename, "sine:", 5)
+ && strncasecmp(filename, "rtsp://", 7)
+ && strncasecmp(filename, "httplive://", 11)
+ && dataSource == NULL) {
fprintf(stderr, "Unable to create data source.\n");
return 1;
}
@@ -687,10 +708,14 @@
isJPEG = true;
}
+ Vector<sp<MediaSource> > mediaSources;
sp<MediaSource> mediaSource;
if (isJPEG) {
mediaSource = new JPEGSource(dataSource);
+ if (gWriteMP4) {
+ mediaSources.push(mediaSource);
+ }
} else if (!strncasecmp("sine:", filename, 5)) {
char *end;
long sampleRate = strtol(filename + 5, &end, 10);
@@ -699,6 +724,9 @@
sampleRate = 44100;
}
mediaSource = new SineSource(sampleRate, 1);
+ if (gWriteMP4) {
+ mediaSources.push(mediaSource);
+ }
} else {
sp<MediaExtractor> extractor;
@@ -718,6 +746,18 @@
extractor = rtspController.get();
syncInfoPresent = false;
+ } else if (!strncasecmp("httplive://", filename, 11)) {
+ String8 uri("http://");
+ uri.append(filename + 11);
+
+ dataSource = new LiveSource(uri.string());
+ dataSource = new NuCachedSource2(dataSource);
+
+ extractor =
+ MediaExtractor::Create(
+ dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS);
+
+ syncInfoPresent = false;
} else {
extractor = MediaExtractor::Create(dataSource);
if (extractor == NULL) {
@@ -728,46 +768,75 @@
size_t numTracks = extractor->countTracks();
- sp<MetaData> meta;
- size_t i;
- for (i = 0; i < numTracks; ++i) {
- meta = extractor->getTrackMetaData(
- i, MediaExtractor::kIncludeExtensiveMetaData);
+ if (gWriteMP4) {
+ bool haveAudio = false;
+ bool haveVideo = false;
+ for (size_t i = 0; i < numTracks; ++i) {
+ sp<MediaSource> source = extractor->getTrack(i);
- const char *mime;
- meta->findCString(kKeyMIMEType, &mime);
+ const char *mime;
+ CHECK(source->getFormat()->findCString(
+ kKeyMIMEType, &mime));
- if (audioOnly && !strncasecmp(mime, "audio/", 6)) {
- break;
+ bool useTrack = false;
+ if (!haveAudio && !strncasecmp("audio/", mime, 6)) {
+ haveAudio = true;
+ useTrack = true;
+ } else if (!haveVideo && !strncasecmp("video/", mime, 6)) {
+ haveVideo = true;
+ useTrack = true;
+ }
+
+ if (useTrack) {
+ mediaSources.push(source);
+
+ if (haveAudio && haveVideo) {
+ break;
+ }
+ }
+ }
+ } else {
+ sp<MetaData> meta;
+ size_t i;
+ for (i = 0; i < numTracks; ++i) {
+ meta = extractor->getTrackMetaData(
+ i, MediaExtractor::kIncludeExtensiveMetaData);
+
+ const char *mime;
+ meta->findCString(kKeyMIMEType, &mime);
+
+ if (audioOnly && !strncasecmp(mime, "audio/", 6)) {
+ break;
+ }
+
+ if (!audioOnly && !strncasecmp(mime, "video/", 6)) {
+ break;
+ }
+
+ meta = NULL;
}
- if (!audioOnly && !strncasecmp(mime, "video/", 6)) {
- break;
+ if (meta == NULL) {
+ fprintf(stderr,
+ "No suitable %s track found. The '-a' option will "
+ "target audio tracks only, the default is to target "
+ "video tracks only.\n",
+ audioOnly ? "audio" : "video");
+ return -1;
}
- meta = NULL;
- }
+ int64_t thumbTimeUs;
+ if (meta->findInt64(kKeyThumbnailTime, &thumbTimeUs)) {
+ printf("thumbnailTime: %lld us (%.2f secs)\n",
+ thumbTimeUs, thumbTimeUs / 1E6);
+ }
- if (meta == NULL) {
- fprintf(stderr,
- "No suitable %s track found. The '-a' option will "
- "target audio tracks only, the default is to target "
- "video tracks only.\n",
- audioOnly ? "audio" : "video");
- return -1;
+ mediaSource = extractor->getTrack(i);
}
-
- int64_t thumbTimeUs;
- if (meta->findInt64(kKeyThumbnailTime, &thumbTimeUs)) {
- printf("thumbnailTime: %lld us (%.2f secs)\n",
- thumbTimeUs, thumbTimeUs / 1E6);
- }
-
- mediaSource = extractor->getTrack(i);
}
if (gWriteMP4) {
- writeSourceToMP4(mediaSource, syncInfoPresent);
+ writeSourcesToMP4(mediaSources, syncInfoPresent);
} else if (seekTest) {
performSeekTest(mediaSource);
} else {
diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java
index 12c05cc..3279e8f 100644
--- a/core/java/android/net/DownloadManager.java
+++ b/core/java/android/net/DownloadManager.java
@@ -214,6 +214,11 @@
"android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
/**
+ * Intent action to launch an activity to display all downloads.
+ */
+ public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
+
+ /**
* Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a
* long) of the download that just completed.
*/
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 97c31fa..8afdcee 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -41,6 +41,12 @@
* -------------------------------------------------------------
*/
+ // Share the event space with ConnectivityService (which we can't see, but
+ // must send events to). If you change these, change ConnectivityService
+ // too.
+ static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1;
+ static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100;
+
/**
* The network state has changed and the NetworkInfo object
* contains the new state.
@@ -63,26 +69,6 @@
public static final int EVENT_RESTORE_DEFAULT_NETWORK = 6;
/**
- * USED by ConnectivityService only
- *
- * msg.what = EVENT_CLEAR_NET_TRANSITION_WAKELOCK
- * msg.arg1 = mNetTransitionWakeLockSerialNumber
- */
- public static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 7;
-
- /**
- * msg.arg1 = network type
- * msg.arg2 = condition (0 bad, 100 good)
- */
- public static final int EVENT_INET_CONDITION_CHANGE = 8;
-
- /**
- * msg.arg1 = network type
- * msg.arg2 = default connection sequence number
- */
- public static final int EVENT_INET_CONDITION_HOLD_END = 9;
-
- /**
* -------------------------------------------------------------
* Control Interface
* -------------------------------------------------------------
diff --git a/core/java/android/provider/AlarmClock.java b/core/java/android/provider/AlarmClock.java
new file mode 100644
index 0000000..b93dfd8
--- /dev/null
+++ b/core/java/android/provider/AlarmClock.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+
+/**
+ * The AlarmClock provider contains an Intent action and extras that can be used
+ * to start an Activity to set a new alarm in an alarm clock application.
+ *
+ * Applications that wish to receive the ACTION_SET_ALARM Intent should create
+ * an activity to handle the Intent that requires the permission
+ * com.android.alarm.permission.SET_ALARM. Applications that wish to create a
+ * new alarm should use
+ * {@link android.content.Context#startActivity Context.startActivity()} so that
+ * the user has the option of choosing which alarm clock application to use.
+ */
+public final class AlarmClock {
+ /**
+ * Activity Action: Set an alarm.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SET_ALARM = "android.intent.action.SET_ALARM";
+
+ /**
+ * Activity Extra: Provide a custom message for the alarm.
+ * <p>
+ * This can be passed as an extra field in the Intent created with
+ * ACTION_SET_ALARM.
+ */
+ public static final String EXTRA_MESSAGE = "android.intent.extra.alarm.MESSAGE";
+
+ /**
+ * Activity Extra: The hour of the alarm being set.
+ * <p>
+ * This value can be passed as an extra field to the Intent created with
+ * ACTION_SET_ALARM. If it is not provided, the behavior is undefined and
+ * is up to the application. The value is an integer and ranges from 0 to
+ * 23.
+ */
+ public static final String EXTRA_HOUR = "android.intent.extra.alarm.HOUR";
+
+ /**
+ * Activity Extra: The minutes of the alarm being set.
+ * <p>
+ * This value can be passed as an extra field to the Intent created with
+ * ACTION_SET_ALARM. If it is not provided, the behavior is undefined and
+ * is up to the application. The value is an integer and ranges from 0 to
+ * 59.
+ */
+ public static final String EXTRA_MINUTES = "android.intent.extra.alarm.MINUTES";
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3d5aff7..96066b7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4176,6 +4176,7 @@
imm.focusOut(this);
}
removeLongPressCallback();
+ removeTapCallback();
onFocusLost();
} else if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
imm.focusIn(this);
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 6917bca..f6a06ce 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -2459,6 +2459,7 @@
if (DBG) Log.d(TAG, "WindowLayout in layoutWindow:" + params);
}
mPendingConfiguration.seq = 0;
+ //Log.d(TAG, ">>>>>> CALLING relayout");
int relayoutResult = sWindowSession.relayout(
mWindow, params,
(int) (mView.mMeasuredWidth * appScale + 0.5f),
@@ -2466,6 +2467,7 @@
viewVisibility, insetsPending, mWinFrame,
mPendingContentInsets, mPendingVisibleInsets,
mPendingConfiguration, mSurface);
+ //Log.d(TAG, "<<<<<< BACK FROM relayout");
if (restore) {
params.restore();
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index bb13f1d..c694ff1 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -4367,9 +4367,13 @@
public void setRemoteViewsAdapter(Intent intent) {
// Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
// service handling the specified intent.
- Intent.FilterComparison fc = new Intent.FilterComparison(intent);
- if (mRemoteAdapter != null && fc.equals(mRemoteAdapter.getRemoteViewsServiceIntent())) {
- return;
+ if (mRemoteAdapter != null) {
+ Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
+ Intent.FilterComparison fcOld = new Intent.FilterComparison(
+ mRemoteAdapter.getRemoteViewsServiceIntent());
+ if (fcNew.equals(fcOld)) {
+ return;
+ }
}
// Otherwise, create a new RemoteViewsAdapter for binding
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index c08adb2..f245933 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -389,7 +389,7 @@
int newWindowStartUnbounded = childIndex - mActiveOffset;
int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
int newWindowStart = Math.max(0, newWindowStartUnbounded);
- int newWindowEnd = Math.min(mAdapter.getCount(), newWindowEndUnbounded);
+ int newWindowEnd = Math.min(mAdapter.getCount() - 1, newWindowEndUnbounded);
// This section clears out any items that are in our mActiveViews list
// but are outside the effective bounds of our window (this is becomes an issue
@@ -592,18 +592,18 @@
*/
private SavedState(Parcel in) {
super(in);
- whichChild = in.readInt();
+ this.whichChild = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
- out.writeInt(whichChild);
+ out.writeInt(this.whichChild);
}
@Override
public String toString() {
- return "AdapterViewAnimator.SavedState{ whichChild = " + whichChild + " }";
+ return "AdapterViewAnimator.SavedState{ whichChild = " + this.whichChild + " }";
}
public static final Parcelable.Creator<SavedState> CREATOR
@@ -781,10 +781,13 @@
public void setRemoteViewsAdapter(Intent intent) {
// Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
// service handling the specified intent.
- Intent.FilterComparison fc = new Intent.FilterComparison(intent);
- if (mRemoteViewsAdapter != null &&
- fc.equals(mRemoteViewsAdapter.getRemoteViewsServiceIntent())) {
- return;
+ if (mRemoteViewsAdapter != null) {
+ Intent.FilterComparison fcNew = new Intent.FilterComparison(intent);
+ Intent.FilterComparison fcOld = new Intent.FilterComparison(
+ mRemoteViewsAdapter.getRemoteViewsServiceIntent());
+ if (fcNew.equals(fcOld)) {
+ return;
+ }
}
// Otherwise, create a new RemoteViewsAdapter for binding
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 945ffeb..9d214fc 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -74,6 +74,11 @@
*/
private ArrayList<Action> mActions;
+ /**
+ * A class to keep track of memory usage by this RemoteViews
+ */
+ private MemoryUsageCounter mMemoryUsageCounter;
+
/**
* This flag indicates whether this RemoteViews object is being created from a
@@ -118,6 +123,15 @@
public int describeContents() {
return 0;
}
+
+ /**
+ * Overridden by each class to report on it's own memory usage
+ */
+ public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
+ // We currently only calculate Bitmap memory usage, so by default, don't do anything
+ // here
+ return;
+ }
}
private class SetEmptyView extends Action {
@@ -525,7 +539,7 @@
}
}
}
-
+
int viewId;
boolean targetBackground;
int alpha;
@@ -817,6 +831,35 @@
throw new ActionException(ex);
}
}
+
+ @Override
+ public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
+ // We currently only calculate Bitmap memory usage
+ switch (this.type) {
+ case BITMAP:
+ if (this.value != null) {
+ final Bitmap b = (Bitmap) this.value;
+ final Bitmap.Config c = b.getConfig();
+ int bpp = 4;
+ switch (c) {
+ case ALPHA_8:
+ bpp = 1;
+ break;
+ case RGB_565:
+ case ARGB_4444:
+ bpp = 2;
+ break;
+ case ARGB_8888:
+ bpp = 4;
+ break;
+ }
+ counter.bitmapIncrement(b.getWidth() * b.getHeight() * bpp);
+ }
+ break;
+ default:
+ break;
+ }
+ }
}
/**
@@ -854,6 +897,13 @@
}
}
+ @Override
+ public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
+ if (nestedViews != null) {
+ counter.bitmapIncrement(nestedViews.estimateBitmapMemoryUsage());
+ }
+ }
+
int viewId;
RemoteViews nestedViews;
@@ -861,6 +911,26 @@
}
/**
+ * Simple class used to keep track of memory usage in a RemoteViews.
+ *
+ */
+ private class MemoryUsageCounter {
+ public void clear() {
+ mBitmapHeapMemoryUsage = 0;
+ }
+
+ public void bitmapIncrement(int numBytes) {
+ mBitmapHeapMemoryUsage += numBytes;
+ }
+
+ public int getBitmapHeapMemoryUsage() {
+ return mBitmapHeapMemoryUsage;
+ }
+
+ int mBitmapHeapMemoryUsage;
+ }
+
+ /**
* Create a new RemoteViews object that will display the views contained
* in the specified layout file.
*
@@ -870,6 +940,10 @@
public RemoteViews(String packageName, int layoutId) {
mPackage = packageName;
mLayoutId = layoutId;
+
+ // setup the memory usage statistics
+ mMemoryUsageCounter = new MemoryUsageCounter();
+ recalculateMemoryUsage();
}
/**
@@ -920,6 +994,10 @@
}
}
}
+
+ // setup the memory usage statistics
+ mMemoryUsageCounter = new MemoryUsageCounter();
+ recalculateMemoryUsage();
}
@Override
@@ -928,6 +1006,9 @@
if (mActions != null) {
that.mActions = (ArrayList<Action>)mActions.clone();
}
+
+ // update the memory usage stats of the cloned RemoteViews
+ that.recalculateMemoryUsage();
return that;
}
@@ -939,7 +1020,7 @@
return mLayoutId;
}
- /**
+ /*
* This flag indicates whether this RemoteViews object is being created from a
* RemoteViewsService for use as a child of a widget collection. This flag is used
* to determine whether or not certain features are available, in particular,
@@ -951,6 +1032,28 @@
}
/**
+ * Updates the memory usage statistics.
+ */
+ private void recalculateMemoryUsage() {
+ mMemoryUsageCounter.clear();
+
+ // Accumulate the memory usage for each action
+ if (mActions != null) {
+ final int count = mActions.size();
+ for (int i= 0; i < count; ++i) {
+ mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter);
+ }
+ }
+ }
+
+ /**
+ * Returns an estimate of the bitmap heap memory usage for this RemoteViews.
+ */
+ int estimateBitmapMemoryUsage() {
+ return mMemoryUsageCounter.getBitmapHeapMemoryUsage();
+ }
+
+ /**
* Add an action to be executed on the remote side when apply is called.
*
* @param a The action to add
@@ -960,6 +1063,9 @@
mActions = new ArrayList<Action>();
}
mActions.add(a);
+
+ // update the memory usage stats
+ a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
/**
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 91b4afa..afb56fc 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -16,7 +16,9 @@
package android.widget;
+import java.lang.ref.WeakReference;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
@@ -32,8 +34,8 @@
import android.util.Log;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
import com.android.internal.widget.IRemoteViewsFactory;
@@ -48,15 +50,17 @@
private Context mContext;
private Intent mIntent;
private RemoteViewsAdapterServiceConnection mServiceConnection;
- private RemoteViewsCache mViewCache;
+ private WeakReference<RemoteAdapterConnectionCallback> mCallback;
+ private FixedSizeRemoteViewsCache mCache;
+
+ // The set of requested views that are to be notified when the associated RemoteViews are
+ // loaded.
+ private RemoteViewsFrameLayoutRefSet mRequestedViews;
private HandlerThread mWorkerThread;
// items may be interrupted within the normally processed queues
private Handler mWorkerQueue;
private Handler mMainQueue;
- // items are never dequeued from the priority queue and must run
- private Handler mWorkerPriorityQueue;
- private Handler mMainPriorityQueue;
/**
* An interface for the RemoteAdapter to notify other classes when adapters
@@ -70,60 +74,89 @@
/**
* The service connection that gets populated when the RemoteViewsService is
- * bound.
+ * bound. This must be a static inner class to ensure that no references to the outer
+ * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
+ * garbage collected, and would cause us to leak activities due to the caching mechanism for
+ * FrameLayouts in the adapter).
*/
- private class RemoteViewsAdapterServiceConnection implements ServiceConnection {
+ private static class RemoteViewsAdapterServiceConnection implements ServiceConnection {
private boolean mConnected;
+ private WeakReference<RemoteViewsAdapter> mAdapter;
private IRemoteViewsFactory mRemoteViewsFactory;
- private RemoteAdapterConnectionCallback mCallback;
- public RemoteViewsAdapterServiceConnection(RemoteAdapterConnectionCallback callback) {
- mCallback = callback;
+ public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
+ mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
}
- public void onServiceConnected(ComponentName name, IBinder service) {
+ public void onServiceConnected(ComponentName name,
+ IBinder service) {
mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
mConnected = true;
- // notifyDataSetChanged should be called first, to ensure that the
- // views are not updated twice
- notifyDataSetChanged();
-
- // post a new runnable to load the appropriate data, then callback
- mWorkerPriorityQueue.post(new Runnable() {
+ // Queue up work that we need to do for the callback to run
+ final RemoteViewsAdapter adapter = mAdapter.get();
+ if (adapter == null) return;
+ adapter.mWorkerQueue.post(new Runnable() {
@Override
public void run() {
- // we need to get the viewTypeCount specifically, so just get all the
- // metadata
- mViewCache.requestMetaData();
+ // Call back to the service to notify that the data set changed
+ if (adapter.mServiceConnection.isConnected()) {
+ IRemoteViewsFactory factory =
+ adapter.mServiceConnection.getRemoteViewsFactory();
+ try {
+ // call back to the factory
+ factory.onDataSetChanged();
+ } catch (Exception e) {
+ Log.e(TAG, "Error notifying factory of data set changed in " +
+ "onServiceConnected(): " + e.getMessage());
+ e.printStackTrace();
- // post a runnable to call the callback on the main thread
- mMainPriorityQueue.post(new Runnable() {
- @Override
- public void run() {
- if (mCallback != null)
- mCallback.onRemoteAdapterConnected();
+ // Return early to prevent anything further from being notified
+ // (effectively nothing has changed)
+ return;
}
- });
+
+ // Request meta data so that we have up to date data when calling back to
+ // the remote adapter callback
+ adapter.updateMetaData();
+
+ // Post a runnable to call back to the view to notify it that we have
+ // connected
+ adapter. mMainQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ final RemoteAdapterConnectionCallback callback =
+ adapter.mCallback.get();
+ if (callback != null) {
+ callback.onRemoteAdapterConnected();
+ }
+ }
+ });
+ }
}
});
-
- // start the background loader
- mViewCache.startBackgroundLoader();
}
public void onServiceDisconnected(ComponentName name) {
- mRemoteViewsFactory = null;
mConnected = false;
+ mRemoteViewsFactory = null;
- // clear the main/worker queues
- mMainQueue.removeMessages(0);
+ final RemoteViewsAdapter adapter = mAdapter.get();
+ if (adapter == null) return;
- // stop the background loader
- mViewCache.stopBackgroundLoader();
+ // Clear the main/worker queues
+ adapter.mMainQueue.removeMessages(0);
+ adapter.mWorkerQueue.removeMessages(0);
- if (mCallback != null)
- mCallback.onRemoteAdapterDisconnected();
+ // Clear the cache
+ synchronized (adapter.mCache) {
+ adapter.mCache.reset();
+ }
+
+ final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
+ if (callback != null) {
+ callback.onRemoteAdapterDisconnected();
+ }
}
public IRemoteViewsFactory getRemoteViewsFactory() {
@@ -136,532 +169,400 @@
}
/**
- * An internal cache of remote views.
+ * A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
+ * they are loaded.
*/
- private class RemoteViewsCache {
- private static final String TAG = "RemoteViewsCache";
-
- private RemoteViewsInfo mViewCacheInfo;
- private RemoteViewsIndexInfo[] mViewCache;
- private int[] mTmpViewCacheLoadIndices;
- private LinkedList<Integer> mViewCacheLoadIndices;
- private boolean mBackgroundLoaderEnabled;
-
- // if a user loading view is not provided, then we create a temporary one
- // for the user using the height of the first view
- private RemoteViews mUserLoadingView;
- private RemoteViews mFirstView;
- private int mFirstViewHeight;
-
- // determines when the current cache window needs to be updated with new
- // items (ie. when there is not enough slack)
- private int mViewCacheStartPosition;
- private int mViewCacheEndPosition;
- private int mHalfCacheSize;
- private int mCacheSlack;
- private final float mCacheSlackPercentage = 0.75f;
-
- /**
- * The data structure stored at each index of the cache. Any member
- * that is not invalidated persists throughout the lifetime of the cache.
- */
- private class RemoteViewsIndexInfo {
- FrameLayout flipper;
- RemoteViews view;
- long itemId;
- int typeId;
-
- RemoteViewsIndexInfo() {
- invalidate();
- }
-
- void set(RemoteViews v, long id) {
- view = v;
- itemId = id;
- if (v != null)
- typeId = v.getLayoutId();
- else
- typeId = 0;
- }
-
- void invalidate() {
- view = null;
- itemId = 0;
- typeId = 0;
- }
-
- final boolean isValid() {
- return (view != null);
- }
+ private class RemoteViewsFrameLayout extends FrameLayout {
+ public RemoteViewsFrameLayout(Context context) {
+ super(context);
}
/**
- * Remote adapter metadata. Useful for when we have to lock on something
- * before updating the metadata.
+ * Updates this RemoteViewsFrameLayout depending on the view that was loaded.
+ * @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
+ * successfully.
*/
- private class RemoteViewsInfo {
- int count;
- int viewTypeCount;
- boolean hasStableIds;
- boolean isDataDirty;
- Map<Integer, Integer> mTypeIdIndexMap;
+ public void onRemoteViewsLoaded(RemoteViews view) {
+ // Remove all the children of this layout first
+ removeAllViews();
+ addView(view.apply(getContext(), this));
+ }
+ }
- RemoteViewsInfo() {
- count = 0;
- // by default there is at least one dummy view type
- viewTypeCount = 1;
- hasStableIds = true;
- isDataDirty = false;
- mTypeIdIndexMap = new HashMap<Integer, Integer>();
+ /**
+ * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
+ * adapter that have not yet had their RemoteViews loaded.
+ */
+ private class RemoteViewsFrameLayoutRefSet {
+ private HashMap<Integer, LinkedList<RemoteViewsFrameLayout>> mReferences;
+
+ public RemoteViewsFrameLayoutRefSet() {
+ mReferences = new HashMap<Integer, LinkedList<RemoteViewsFrameLayout>>();
+ }
+
+ /**
+ * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
+ */
+ public void add(int position, RemoteViewsFrameLayout layout) {
+ final Integer pos = position;
+ LinkedList<RemoteViewsFrameLayout> refs;
+
+ // Create the list if necessary
+ if (mReferences.containsKey(pos)) {
+ refs = mReferences.get(pos);
+ } else {
+ refs = new LinkedList<RemoteViewsFrameLayout>();
+ mReferences.put(pos, refs);
}
+
+ // Add the references to the list
+ refs.add(layout);
}
- public RemoteViewsCache(int halfCacheSize) {
- mHalfCacheSize = halfCacheSize;
- mCacheSlack = Math.round(mCacheSlackPercentage * mHalfCacheSize);
- mViewCacheStartPosition = 0;
- mViewCacheEndPosition = -1;
- mBackgroundLoaderEnabled = false;
-
- // initialize the cache
- int cacheSize = 2 * mHalfCacheSize + 1;
- mViewCacheInfo = new RemoteViewsInfo();
- mViewCache = new RemoteViewsIndexInfo[cacheSize];
- for (int i = 0; i < mViewCache.length; ++i) {
- mViewCache[i] = new RemoteViewsIndexInfo();
- }
- mTmpViewCacheLoadIndices = new int[cacheSize];
- mViewCacheLoadIndices = new LinkedList<Integer>();
- }
-
- private final boolean contains(int position) {
- return (mViewCacheStartPosition <= position) && (position <= mViewCacheEndPosition);
- }
-
- private final boolean containsAndIsValid(int position) {
- if (contains(position)) {
- RemoteViewsIndexInfo indexInfo = mViewCache[getCacheIndex(position)];
- if (indexInfo.isValid()) {
- return true;
+ /**
+ * Notifies each of the RemoteViewsFrameLayouts associated with a particular position that
+ * the associated RemoteViews has loaded.
+ */
+ public void notifyOnRemoteViewsLoaded(int position, RemoteViews view, int typeId) {
+ final Integer pos = position;
+ if (mReferences.containsKey(pos)) {
+ // Notify all the references for that position of the newly loaded RemoteViews
+ final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
+ for (final RemoteViewsFrameLayout ref : refs) {
+ ref.onRemoteViewsLoaded(view);
}
+ refs.clear();
+
+ // Remove this set from the original mapping
+ mReferences.remove(pos);
}
- return false;
}
- private final int getCacheIndex(int position) {
- // take the modulo of the position
- final int cacheSize = mViewCache.length;
- return (cacheSize + (position % cacheSize)) % cacheSize;
+ /**
+ * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
+ */
+ public void clear() {
+ // We currently just clear the references, and leave all the previous layouts returned
+ // in their default state of the loading view.
+ mReferences.clear();
+ }
+ }
+
+ /**
+ * The meta-data associated with the cache in it's current state.
+ */
+ private class RemoteViewsMetaData {
+ int count;
+ int viewTypeCount;
+ boolean hasStableIds;
+ boolean isDataDirty;
+
+ // Used to determine how to construct loading views. If a loading view is not specified
+ // by the user, then we try and load the first view, and use its height as the height for
+ // the default loading view.
+ RemoteViews mUserLoadingView;
+ RemoteViews mFirstView;
+ int mFirstViewHeight;
+
+ // A mapping from type id to a set of unique type ids
+ private Map<Integer, Integer> mTypeIdIndexMap;
+
+ public RemoteViewsMetaData() {
+ reset();
}
- public void requestMetaData() {
- if (mServiceConnection.isConnected()) {
- try {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+ public void reset() {
+ count = 0;
+ // by default there is at least one dummy view type
+ viewTypeCount = 1;
+ hasStableIds = true;
+ isDataDirty = false;
+ mUserLoadingView = null;
+ mFirstView = null;
+ mFirstViewHeight = 0;
+ mTypeIdIndexMap = new HashMap<Integer, Integer>();
+ }
- // get the properties/first view (so that we can use it to
- // measure our dummy views)
- boolean hasStableIds = factory.hasStableIds();
- int viewTypeCount = factory.getViewTypeCount();
- int count = factory.getCount();
- RemoteViews loadingView = factory.getLoadingView();
- RemoteViews firstView = null;
- if ((count > 0) && (loadingView == null)) {
- firstView = factory.getViewAt(0);
- }
- synchronized (mViewCacheInfo) {
- RemoteViewsInfo info = mViewCacheInfo;
- info.hasStableIds = hasStableIds;
- info.viewTypeCount = viewTypeCount + 1;
- info.count = count;
- mUserLoadingView = loadingView;
- if (firstView != null) {
- mFirstView = firstView;
- mFirstViewHeight = -1;
- }
- }
- } catch (Exception e) {
- // print the error
- Log.e(TAG, "Error in requestMetaData(): " + e.getMessage());
+ public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
+ mUserLoadingView = loadingView;
+ if (firstView != null) {
+ mFirstView = firstView;
+ mFirstViewHeight = -1;
+ }
+ }
- // reset any members after the failed call
- synchronized (mViewCacheInfo) {
- RemoteViewsInfo info = mViewCacheInfo;
- info.hasStableIds = false;
- info.viewTypeCount = 1;
- info.count = 0;
- mUserLoadingView = null;
+ public int getMappedViewType(int typeId) {
+ if (mTypeIdIndexMap.containsKey(typeId)) {
+ return mTypeIdIndexMap.get(typeId);
+ } else {
+ // We +1 because the loading view always has view type id of 0
+ int incrementalTypeId = mTypeIdIndexMap.size() + 1;
+ mTypeIdIndexMap.put(typeId, incrementalTypeId);
+ return incrementalTypeId;
+ }
+ }
+
+ private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
+ ViewGroup parent) {
+ // Create and return a new FrameLayout, and setup the references for this position
+ final Context context = parent.getContext();
+ RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
+
+ // Create a new loading view
+ synchronized (mCache) {
+ if (mUserLoadingView != null) {
+ // A user-specified loading view
+ View loadingView = mUserLoadingView.apply(parent.getContext(), parent);
+ loadingView.setTag(new Integer(0));
+ layout.addView(loadingView);
+ } else {
+ // A default loading view
+ // Use the size of the first row as a guide for the size of the loading view
+ if (mFirstViewHeight < 0) {
+ View firstView = mFirstView.apply(parent.getContext(), parent);
+ firstView.measure(
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ mFirstViewHeight = firstView.getMeasuredHeight();
mFirstView = null;
- mFirstViewHeight = -1;
- }
- }
- }
- }
-
- protected void onNotifyDataSetChanged() {
- // we mark the data as dirty so that the next call to fetch views will result in
- // an onDataSetDirty() call from the adapter
- synchronized (mViewCacheInfo) {
- mViewCacheInfo.isDataDirty = true;
- }
- }
-
- private void updateNotifyDataSetChanged() {
- // actually calls through to the factory to notify it to update
- if (mServiceConnection.isConnected()) {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
- try {
- // call back to the factory
- factory.onDataSetChanged();
- } catch (Exception e) {
- // print the error
- Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-
- // return early to prevent container from being notified (nothing has changed)
- return;
- }
- }
-
- // re-request the new metadata (only after the notification to the factory)
- requestMetaData();
-
- // post a new runnable on the main thread to propagate the notification back
- // to the base adapter
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- completeNotifyDataSetChanged();
- }
- });
- }
-
- protected void updateRemoteViewsInfo(int position) {
- if (mServiceConnection.isConnected()) {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
- // load the item information
- RemoteViews remoteView = null;
- long itemId = 0;
- try {
- remoteView = factory.getViewAt(position);
- itemId = factory.getItemId(position);
- } catch (Exception e) {
- // print the error
- Log.e(TAG, "Error in updateRemoteViewsInfo(" + position + "): " +
- e.getMessage());
- e.printStackTrace();
-
- // return early to prevent additional work in re-centering the view cache, and
- // swapping from the loading view
- return;
- }
-
- synchronized (mViewCache) {
- // skip if the window has moved
- if (position < mViewCacheStartPosition || position > mViewCacheEndPosition)
- return;
-
- final int positionIndex = position;
- final int cacheIndex = getCacheIndex(position);
- mViewCache[cacheIndex].set(remoteView, itemId);
-
- // notify the main thread when done loading
- // flush pending updates
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- // swap the loader view for this view
- synchronized (mViewCache) {
- if (containsAndIsValid(positionIndex)) {
- RemoteViewsIndexInfo indexInfo = mViewCache[cacheIndex];
- FrameLayout flipper = indexInfo.flipper;
-
- // update the flipper
- flipper.getChildAt(0).setVisibility(View.GONE);
- boolean addNewView = true;
- if (flipper.getChildCount() > 1) {
- View v = flipper.getChildAt(1);
- int typeId = ((Integer) v.getTag()).intValue();
- if (typeId == indexInfo.typeId) {
- // we can reapply since it is the same type
- indexInfo.view.reapply(mContext, v);
- v.setVisibility(View.VISIBLE);
- if (v.getAnimation() != null)
- v.buildDrawingCache();
- addNewView = false;
- } else {
- flipper.removeViewAt(1);
- }
- }
- if (addNewView) {
- View v = indexInfo.view.apply(mContext, flipper);
- v.setTag(new Integer(indexInfo.typeId));
- flipper.addView(v);
- }
- }
- }
- }
- });
- }
- }
- }
-
- private RemoteViewsIndexInfo requestCachedIndexInfo(final int position) {
- int indicesToLoadCount = 0;
-
- synchronized (mViewCache) {
- if (containsAndIsValid(position)) {
- // return the info if it exists in the window and is loaded
- return mViewCache[getCacheIndex(position)];
- }
-
- // if necessary update the window and load the new information
- int centerPosition = (mViewCacheEndPosition + mViewCacheStartPosition) / 2;
- if ((mViewCacheEndPosition <= mViewCacheStartPosition) || (Math.abs(position - centerPosition) > mCacheSlack)) {
- int newStartPosition = position - mHalfCacheSize;
- int newEndPosition = position + mHalfCacheSize;
- int frameSize = mHalfCacheSize / 4;
- int frameCount = (int) Math.ceil(mViewCache.length / (float) frameSize);
-
- // prune/add before the current start position
- int effectiveStart = Math.max(newStartPosition, 0);
- int effectiveEnd = Math.min(newEndPosition, getCount() - 1);
-
- // invalidate items in the queue
- int overlapStart = Math.max(mViewCacheStartPosition, effectiveStart);
- int overlapEnd = Math.min(Math.max(mViewCacheStartPosition, mViewCacheEndPosition), effectiveEnd);
- for (int i = 0; i < (frameSize * frameCount); ++i) {
- int index = newStartPosition + ((i % frameSize) * frameCount + (i / frameSize));
-
- if (index <= newEndPosition) {
- if ((overlapStart <= index) && (index <= overlapEnd)) {
- // load the stuff in the middle that has not already
- // been loaded
- if (!mViewCache[getCacheIndex(index)].isValid()) {
- mTmpViewCacheLoadIndices[indicesToLoadCount++] = index;
- }
- } else if ((effectiveStart <= index) && (index <= effectiveEnd)) {
- // invalidate and load all new effective items
- mViewCache[getCacheIndex(index)].invalidate();
- mTmpViewCacheLoadIndices[indicesToLoadCount++] = index;
- } else {
- // invalidate all other cache indices (outside the effective start/end)
- // but don't load
- mViewCache[getCacheIndex(index)].invalidate();
- }
- }
}
- mViewCacheStartPosition = newStartPosition;
- mViewCacheEndPosition = newEndPosition;
+ // Compose the loading view text
+ TextView textView = new TextView(parent.getContext());
+ textView.setText(com.android.internal.R.string.loading);
+ textView.setHeight(mFirstViewHeight);
+ textView.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
+ textView.setTextSize(18.0f);
+ textView.setTextColor(Color.argb(96, 255, 255, 255));
+ textView.setShadowLayer(2.0f, 0.0f, 1.0f, Color.BLACK);
+ textView.setTag(new Integer(0));
+
+ layout.addView(textView);
}
}
- // post items to be loaded
- int length = 0;
- synchronized (mViewCacheInfo) {
- length = mViewCacheInfo.count;
- }
- if (indicesToLoadCount > 0) {
- synchronized (mViewCacheLoadIndices) {
- mViewCacheLoadIndices.clear();
- for (int i = 0; i < indicesToLoadCount; ++i) {
- final int index = mTmpViewCacheLoadIndices[i];
- if (0 <= index && index < length) {
- mViewCacheLoadIndices.addLast(index);
- }
- }
- }
+ return layout;
+ }
+ }
+
+ /**
+ * The meta-data associated with a single item in the cache.
+ */
+ private class RemoteViewsIndexMetaData {
+ int typeId;
+ long itemId;
+
+ public RemoteViewsIndexMetaData(RemoteViews v, long itemId) {
+ set(v, itemId);
+ }
+
+ public void set(RemoteViews v, long id) {
+ itemId = id;
+ if (v != null)
+ typeId = v.getLayoutId();
+ else
+ typeId = 0;
+ }
+ }
+
+ /**
+ *
+ */
+ private class FixedSizeRemoteViewsCache {
+ private static final String TAG = "FixedSizeRemoteViewsCache";
+
+ // The meta data related to all the RemoteViews, ie. count, is stable, etc.
+ private RemoteViewsMetaData mMetaData;
+
+ // The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
+ // greater than or equal to the set of RemoteViews.
+ // Note: The reason that we keep this separate from the RemoteViews cache below is that this
+ // we still need to be able to access the mapping of position to meta data, without keeping
+ // the heavy RemoteViews around. The RemoteViews cache is trimmed to fixed constraints wrt.
+ // memory and size, but this metadata cache will retain information until the data at the
+ // position is guaranteed as not being necessary any more (usually on notifyDataSetChanged).
+ private HashMap<Integer, RemoteViewsIndexMetaData> mIndexMetaData;
+
+ // The cache of actual RemoteViews, which may be pruned if the cache gets too large, or uses
+ // too much memory.
+ private HashMap<Integer, RemoteViews> mIndexRemoteViews;
+
+ // The set of indices that have been explicitly requested by the collection view
+ private HashSet<Integer> mRequestedIndices;
+
+ // The set of indices to load, including those explicitly requested, as well as those
+ // determined by the preloading algorithm to be prefetched
+ private HashSet<Integer> mLoadIndices;
+
+ // The lower and upper bounds of the preloaded range
+ private int mPreloadLowerBound;
+ private int mPreloadUpperBound;
+
+ // The bounds of this fixed cache, we will try and fill as many items into the cache up to
+ // the maxCount number of items, or the maxSize memory usage.
+ // The maxCountSlack is used to determine if a new position in the cache to be loaded is
+ // sufficiently ouside the old set, prompting a shifting of the "window" of items to be
+ // preloaded.
+ private int mMaxCount;
+ private int mMaxCountSlack;
+ private static final float sMaxCountSlackPercent = 0.75f;
+ private static final int sMaxMemoryUsage = 1024 * 1024;
+
+ public FixedSizeRemoteViewsCache(int maxCacheSize) {
+ mMaxCount = maxCacheSize;
+ mMaxCountSlack = Math.round(sMaxCountSlackPercent * (mMaxCount / 2));
+ mPreloadLowerBound = 0;
+ mPreloadUpperBound = -1;
+ mMetaData = new RemoteViewsMetaData();
+ mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
+ mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
+ mRequestedIndices = new HashSet<Integer>();
+ mLoadIndices = new HashSet<Integer>();
+ }
+
+ public void insert(int position, RemoteViews v, long itemId) {
+ // Trim the cache if we go beyond the count
+ if (mIndexRemoteViews.size() >= mMaxCount) {
+ mIndexRemoteViews.remove(getFarthestPositionFrom(position));
}
- // return null so that a dummy view can be retrieved
+ // Trim the cache if we go beyond the available memory size constraints
+ while (getRemoteViewsBitmapMemoryUsage() >= sMaxMemoryUsage) {
+ // Note: This is currently the most naive mechanism for deciding what to prune when
+ // we hit the memory limit. In the future, we may want to calculate which index to
+ // remove based on both its position as well as it's current memory usage, as well
+ // as whether it was directly requested vs. whether it was preloaded by our caching
+ // mechanism.
+ mIndexRemoteViews.remove(getFarthestPositionFrom(position));
+ }
+
+ // Update the metadata cache
+ if (mIndexMetaData.containsKey(position)) {
+ final RemoteViewsIndexMetaData metaData = mIndexMetaData.get(position);
+ metaData.set(v, itemId);
+ } else {
+ mIndexMetaData.put(position, new RemoteViewsIndexMetaData(v, itemId));
+ }
+ mIndexRemoteViews.put(position, v);
+ }
+
+ public RemoteViewsMetaData getMetaData() {
+ return mMetaData;
+ }
+ public RemoteViews getRemoteViewsAt(int position) {
+ if (mIndexRemoteViews.containsKey(position)) {
+ return mIndexRemoteViews.get(position);
+ }
+ return null;
+ }
+ public RemoteViewsIndexMetaData getMetaDataAt(int position) {
+ if (mIndexMetaData.containsKey(position)) {
+ return mIndexMetaData.get(position);
+ }
return null;
}
- public View getView(int position, View convertView, ViewGroup parent) {
- if (mServiceConnection.isConnected()) {
- // create the flipper views if necessary (we have to do this now
- // for all the flippers while we have the reference to the parent)
- initializeLoadingViews(parent);
-
- // request the item from the cache (queueing it to load if not
- // in the cache already)
- RemoteViewsIndexInfo indexInfo = requestCachedIndexInfo(position);
-
- // update the flipper appropriately
- synchronized (mViewCache) {
- int cacheIndex = getCacheIndex(position);
- FrameLayout flipper = mViewCache[cacheIndex].flipper;
- flipper.setVisibility(View.VISIBLE);
- flipper.setAlpha(1.0f);
-
- if (indexInfo == null) {
- // hide the item view and show the loading view
- flipper.getChildAt(0).setVisibility(View.VISIBLE);
- for (int i = 1; i < flipper.getChildCount(); ++i) {
- flipper.getChildAt(i).setVisibility(View.GONE);
- }
- } else {
- // hide the loading view and show the item view
- for (int i = 0; i < flipper.getChildCount() - 1; ++i) {
- flipper.getChildAt(i).setVisibility(View.GONE);
- }
- flipper.getChildAt(flipper.getChildCount() - 1).setVisibility(View.VISIBLE);
- }
- return flipper;
+ private int getRemoteViewsBitmapMemoryUsage() {
+ // Calculate the memory usage of all the RemoteViews bitmaps being cached
+ int mem = 0;
+ for (Integer i : mIndexRemoteViews.keySet()) {
+ final RemoteViews v = mIndexRemoteViews.get(i);
+ mem += v.estimateBitmapMemoryUsage();
+ }
+ return mem;
+ }
+ private int getFarthestPositionFrom(int pos) {
+ // Find the index farthest away and remove that
+ int maxDist = 0;
+ int maxDistIndex = -1;
+ for (int i : mIndexRemoteViews.keySet()) {
+ int dist = Math.abs(i-pos);
+ if (dist > maxDist) {
+ maxDistIndex = i;
+ maxDist = dist;
}
}
- return new View(mContext);
+ return maxDistIndex;
}
- private void initializeLoadingViews(ViewGroup parent) {
- // ensure that the cache has the appropriate initial flipper
- synchronized (mViewCache) {
- if (mViewCache[0].flipper == null) {
- for (int i = 0; i < mViewCache.length; ++i) {
- FrameLayout flipper = new FrameLayout(mContext);
- if (mUserLoadingView != null) {
- // use the user-specified loading view
- flipper.addView(mUserLoadingView.apply(mContext, parent));
- } else {
- // calculate the original size of the first row for the loader view
- synchronized (mViewCacheInfo) {
- if (mFirstViewHeight < 0) {
- View firstView = mFirstView.apply(mContext, parent);
- firstView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mFirstViewHeight = firstView.getMeasuredHeight();
- }
- }
-
- // construct a new loader and add it to the flipper as the fallback
- // default view
- TextView textView = new TextView(mContext);
- textView.setText("Loading...");
- textView.setHeight(mFirstViewHeight);
- textView.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
- textView.setTextSize(18.0f);
- textView.setTextColor(Color.argb(96, 255, 255, 255));
- textView.setShadowLayer(2.0f, 0.0f, 1.0f, Color.BLACK);
-
- flipper.addView(textView);
- }
- mViewCache[i].flipper = flipper;
- }
+ public void queueRequestedPositionToLoad(int position) {
+ synchronized (mLoadIndices) {
+ mRequestedIndices.add(position);
+ mLoadIndices.add(position);
+ }
+ }
+ public void queuePositionsToBePreloadedFromRequestedPosition(int position) {
+ // Check if we need to preload any items
+ if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
+ int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
+ if (Math.abs(position - center) < mMaxCountSlack) {
+ return;
}
}
- }
- public void startBackgroundLoader() {
- // initialize the worker runnable
- mBackgroundLoaderEnabled = true;
- mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- while (mBackgroundLoaderEnabled) {
- // notify the RemoteViews factory if necessary
- boolean isDataDirty = false;
- synchronized (mViewCacheInfo) {
- isDataDirty = mViewCacheInfo.isDataDirty;
- mViewCacheInfo.isDataDirty = false;
- }
- if (isDataDirty) {
- updateNotifyDataSetChanged();
- }
-
- int index = -1;
- synchronized (mViewCacheLoadIndices) {
- if (!mViewCacheLoadIndices.isEmpty()) {
- index = mViewCacheLoadIndices.removeFirst();
- }
- }
- if (index < 0) {
- // there were no items to load, so sleep for a bit
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- } else {
- // otherwise, try and load the item
- updateRemoteViewsInfo(index);
- }
- }
- }
- });
- }
-
- public void stopBackgroundLoader() {
- // clear the items to be loaded
- mBackgroundLoaderEnabled = false;
- synchronized (mViewCacheLoadIndices) {
- mViewCacheLoadIndices.clear();
+ int count = 0;
+ synchronized (mMetaData) {
+ count = mMetaData.count;
}
- }
+ synchronized (mLoadIndices) {
+ mLoadIndices.clear();
- public long getItemId(int position) {
- synchronized (mViewCache) {
- if (containsAndIsValid(position)) {
- return mViewCache[getCacheIndex(position)].itemId;
- }
- }
- return 0;
- }
+ // Add all the requested indices
+ mLoadIndices.addAll(mRequestedIndices);
- public int getItemViewType(int position) {
- // synchronize to ensure that the type id/index map is updated synchronously
- synchronized (mViewCache) {
- if (containsAndIsValid(position)) {
- int viewId = mViewCache[getCacheIndex(position)].typeId;
- Map<Integer, Integer> typeMap = mViewCacheInfo.mTypeIdIndexMap;
- // we +1 because the default dummy view get view type 0
- if (typeMap.containsKey(viewId)) {
- return typeMap.get(viewId);
- } else {
- int newIndex = typeMap.size() + 1;
- typeMap.put(viewId, newIndex);
- return newIndex;
- }
- }
- }
- // return the type of the default item
- return 0;
- }
-
- public int getCount() {
- synchronized (mViewCacheInfo) {
- return mViewCacheInfo.count;
- }
- }
-
- public int getViewTypeCount() {
- synchronized (mViewCacheInfo) {
- return mViewCacheInfo.viewTypeCount;
- }
- }
-
- public boolean hasStableIds() {
- synchronized (mViewCacheInfo) {
- return mViewCacheInfo.hasStableIds;
- }
- }
-
- public void flushCache() {
- // clear the items to be loaded
- synchronized (mViewCacheLoadIndices) {
- mViewCacheLoadIndices.clear();
- }
-
- synchronized (mViewCache) {
- // flush the internal cache and invalidate the adapter for future loads
- mMainQueue.removeMessages(0);
-
- for (int i = 0; i < mViewCache.length; ++i) {
- mViewCache[i].invalidate();
+ // Add all the preload indices
+ int halfMaxCount = mMaxCount / 2;
+ mPreloadLowerBound = position - halfMaxCount;
+ mPreloadUpperBound = position + halfMaxCount;
+ int effectiveLowerBound = Math.max(0, mPreloadLowerBound);
+ int effectiveUpperBound = Math.min(mPreloadUpperBound, count - 1);
+ for (int i = effectiveLowerBound; i <= effectiveUpperBound; ++i) {
+ mLoadIndices.add(i);
}
- mViewCacheStartPosition = 0;
- mViewCacheEndPosition = -1;
+ // But remove all the indices that have already been loaded and are cached
+ mLoadIndices.removeAll(mIndexRemoteViews.keySet());
+ }
+ }
+ public int getNextIndexToLoad() {
+ // We try and prioritize items that have been requested directly, instead
+ // of items that are loaded as a result of the caching mechanism
+ synchronized (mLoadIndices) {
+ // Prioritize requested indices to be loaded first
+ if (!mRequestedIndices.isEmpty()) {
+ Integer i = mRequestedIndices.iterator().next();
+ mRequestedIndices.remove(i);
+ mLoadIndices.remove(i);
+ return i.intValue();
+ }
+
+ // Otherwise, preload other indices as necessary
+ if (!mLoadIndices.isEmpty()) {
+ Integer i = mLoadIndices.iterator().next();
+ mLoadIndices.remove(i);
+ return i.intValue();
+ }
+
+ return -1;
+ }
+ }
+
+ public boolean containsRemoteViewAt(int position) {
+ return mIndexRemoteViews.containsKey(position);
+ }
+ public boolean containsMetaDataAt(int position) {
+ return mIndexMetaData.containsKey(position);
+ }
+
+ public void reset() {
+ mPreloadLowerBound = 0;
+ mPreloadUpperBound = -1;
+ mIndexRemoteViews.clear();
+ mIndexMetaData.clear();
+ mMetaData.reset();
+ synchronized (mLoadIndices) {
+ mRequestedIndices.clear();
+ mLoadIndices.clear();
}
}
}
@@ -672,24 +573,126 @@
if (mIntent == null) {
throw new IllegalArgumentException("Non-null Intent must be specified.");
}
+ mRequestedViews = new RemoteViewsFrameLayoutRefSet();
// initialize the worker thread
mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
mWorkerThread.start();
mWorkerQueue = new Handler(mWorkerThread.getLooper());
- mWorkerPriorityQueue = new Handler(mWorkerThread.getLooper());
mMainQueue = new Handler(Looper.myLooper());
- mMainPriorityQueue = new Handler(Looper.myLooper());
// initialize the cache and the service connection on startup
- mViewCache = new RemoteViewsCache(25);
- mServiceConnection = new RemoteViewsAdapterServiceConnection(callback);
+ mCache = new FixedSizeRemoteViewsCache(50);
+ mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
+ mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
requestBindService();
}
- protected void finalize() throws Throwable {
- // remember to unbind from the service when finalizing
- unbindService();
+ private void loadNextIndexInBackground() {
+ mWorkerQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ boolean isDataDirty = false;
+
+ // If the data set has changed, then notify the remote factory so that it can
+ // update its internals first.
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ isDataDirty = metaData.isDataDirty;
+ metaData.isDataDirty = false;
+ }
+ if (isDataDirty) {
+ completeNotifyDataSetChanged();
+ }
+
+ // Get the next index to load
+ int position = -1;
+ synchronized (mCache) {
+ position = mCache.getNextIndexToLoad();
+ }
+ if (position > -1) {
+ // Load the item, and notify any existing RemoteViewsFrameLayouts
+ updateRemoteViews(position);
+
+ // Queue up for the next one to load
+ loadNextIndexInBackground();
+ }
+ }
+ });
+ }
+
+ private void updateMetaData() {
+ if (mServiceConnection.isConnected()) {
+ try {
+ IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+
+ // get the properties/first view (so that we can use it to
+ // measure our dummy views)
+ boolean hasStableIds = factory.hasStableIds();
+ int viewTypeCount = factory.getViewTypeCount();
+ int count = factory.getCount();
+ RemoteViews loadingView = factory.getLoadingView();
+ RemoteViews firstView = null;
+ if ((count > 0) && (loadingView == null)) {
+ firstView = factory.getViewAt(0);
+ }
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ metaData.hasStableIds = hasStableIds;
+ metaData.viewTypeCount = viewTypeCount + 1;
+ metaData.count = count;
+ metaData.setLoadingViewTemplates(loadingView, firstView);
+ }
+ } catch (Exception e) {
+ // print the error
+ Log.e(TAG, "Error in requestMetaData(): " + e.getMessage());
+
+ // reset any members after the failed call
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ metaData.reset();
+ }
+ }
+ }
+ }
+
+ private void updateRemoteViews(final int position) {
+ if (mServiceConnection.isConnected()) {
+ IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+
+ // Load the item information from the remote service
+ RemoteViews remoteViews = null;
+ long itemId = 0;
+ try {
+ remoteViews = factory.getViewAt(position);
+ itemId = factory.getItemId(position);
+ } catch (Exception e) {
+ // Print the error
+ Log.e(TAG, "Error in updateRemoteViewsInfo(" + position + "): " +
+ e.getMessage());
+ e.printStackTrace();
+
+ // Return early to prevent additional work in re-centering the view cache, and
+ // swapping from the loading view
+ return;
+ }
+
+ synchronized (mCache) {
+ // Cache the RemoteViews we loaded
+ mCache.insert(position, remoteViews, itemId);
+
+ // Notify all the views that we have previously returned for this index that
+ // there is new data for it.
+ final RemoteViews rv = remoteViews;
+ final int typeId = mCache.getMetaDataAt(position).typeId;
+ mMainQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
+ }
+ });
+ }
+ }
}
public Intent getRemoteViewsServiceIntent() {
@@ -698,37 +701,132 @@
public int getCount() {
requestBindService();
- return mViewCache.getCount();
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ return metaData.count;
+ }
}
public Object getItem(int position) {
- // disallow arbitrary object to be associated with an item for the time being
+ // Disallow arbitrary object to be associated with an item for the time being
return null;
}
public long getItemId(int position) {
requestBindService();
- return mViewCache.getItemId(position);
+ synchronized (mCache) {
+ if (mCache.containsMetaDataAt(position)) {
+ return mCache.getMetaDataAt(position).itemId;
+ }
+ return 0;
+ }
}
public int getItemViewType(int position) {
requestBindService();
- return mViewCache.getItemViewType(position);
+ int typeId = 0;
+ synchronized (mCache) {
+ if (mCache.containsMetaDataAt(position)) {
+ typeId = mCache.getMetaDataAt(position).typeId;
+ } else {
+ return 0;
+ }
+ }
+
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ return metaData.getMappedViewType(typeId);
+ }
+ }
+
+ /**
+ * Returns the item type id for the specified convert view. Returns -1 if the convert view
+ * is invalid.
+ */
+ private int getConvertViewTypeId(View convertView) {
+ int typeId = -1;
+ if (convertView != null && convertView.getTag() != null) {
+ typeId = (Integer) convertView.getTag();
+ }
+ return typeId;
}
public View getView(int position, View convertView, ViewGroup parent) {
requestBindService();
- return mViewCache.getView(position, convertView, parent);
+ if (mServiceConnection.isConnected()) {
+ // "Request" an index so that we can queue it for loading, initiate subsequent
+ // preloading, etc.
+ synchronized (mCache) {
+ // Queue up other indices to be preloaded based on this position
+ mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
+
+ RemoteViewsFrameLayout layout = (RemoteViewsFrameLayout) convertView;
+ View convertViewChild = null;
+ int convertViewTypeId = 0;
+ if (convertView != null) {
+ convertViewChild = layout.getChildAt(0);
+ convertViewTypeId = getConvertViewTypeId(convertViewChild);
+ }
+
+ // Second, we try and retrieve the RemoteViews from the cache, returning a loading
+ // view and queueing it to be loaded if it has not already been loaded.
+ if (mCache.containsRemoteViewAt(position)) {
+ Context context = parent.getContext();
+ RemoteViews rv = mCache.getRemoteViewsAt(position);
+ int typeId = mCache.getMetaDataAt(position).typeId;
+
+ // Reuse the convert view where possible
+ if (convertView != null) {
+ if (convertViewTypeId == typeId) {
+ rv.reapply(context, convertViewChild);
+ return convertView;
+ }
+ }
+
+ // Otherwise, create a new view to be returned
+ View newView = rv.apply(context, parent);
+ newView.setTag(new Integer(typeId));
+ if (convertView != null) {
+ layout.removeAllViews();
+ } else {
+ layout = new RemoteViewsFrameLayout(context);
+ }
+ layout.addView(newView);
+ return layout;
+ } else {
+ // If the cache does not have the RemoteViews at this position, then create a
+ // loading view and queue the actual position to be loaded in the background
+ RemoteViewsFrameLayout loadingView = null;
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ loadingView = metaData.createLoadingView(position, convertView, parent);
+ }
+
+ mRequestedViews.add(position, loadingView);
+ mCache.queueRequestedPositionToLoad(position);
+ loadNextIndexInBackground();
+
+ return loadingView;
+ }
+ }
+ }
+ return new View(parent.getContext());
}
public int getViewTypeCount() {
requestBindService();
- return mViewCache.getViewTypeCount();
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ return metaData.viewTypeCount;
+ }
}
public boolean hasStableIds() {
requestBindService();
- return mViewCache.hasStableIds();
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ return metaData.hasStableIds;
+ }
}
public boolean isEmpty() {
@@ -736,14 +834,49 @@
}
public void notifyDataSetChanged() {
- // flush the cache so that we can reload new items from the service
- mViewCache.flushCache();
+ synchronized (mCache) {
+ // Flush the cache so that we can reload new items from the service
+ mCache.reset();
+ }
- // notify the factory that it's data may no longer be valid
- mViewCache.onNotifyDataSetChanged();
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ // Set flag to calls the remote factory's onDataSetChanged() on the next worker loop
+ metaData.isDataDirty = true;
+ }
+
+ // Note: we do not call super.notifyDataSetChanged() until the RemoteViewsFactory has had
+ // a chance to update itself, and return new meta data associated with the new data. After
+ // which completeNotifyDataSetChanged() is called.
}
- public void completeNotifyDataSetChanged() {
+ private void completeNotifyDataSetChanged() {
+ // Complete the actual notifyDataSetChanged() call initiated earlier
+ if (mServiceConnection.isConnected()) {
+ IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+ try {
+ factory.onDataSetChanged();
+ } catch (Exception e) {
+ Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+
+ // Return early to prevent from further being notified (since nothing has changed)
+ return;
+ }
+ }
+
+ // Re-request the new metadata (only after the notification to the factory)
+ updateMetaData();
+
+ // Propagate the notification back to the base adapter
+ mMainQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ superNotifyDataSetChanged();
+ }
+ });
+ }
+
+ private void superNotifyDataSetChanged() {
super.notifyDataSetChanged();
}
@@ -755,10 +888,4 @@
return mServiceConnection.isConnected();
}
-
- private void unbindService() {
- if (mServiceConnection.isConnected()) {
- mContext.unbindService(mServiceConnection);
- }
- }
}
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 9d215b7..8409adc 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -252,21 +252,23 @@
lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this);
lpJniStorage->mStreamType = atStreamType;
-
- jint* nSession = NULL;
- if (jSession) {
- nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
- if (nSession == NULL) {
- LOGE("Error creating AudioTrack: Error retrieving session id pointer");
- delete lpJniStorage;
- return AUDIOTRACK_ERROR;
- }
- } else {
+
+ if (jSession == NULL) {
LOGE("Error creating AudioTrack: invalid session ID pointer");
delete lpJniStorage;
return AUDIOTRACK_ERROR;
}
+ jint* nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
+ if (nSession == NULL) {
+ LOGE("Error creating AudioTrack: Error retrieving session id pointer");
+ delete lpJniStorage;
+ return AUDIOTRACK_ERROR;
+ }
+ int sessionId = nSession[0];
+ env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
+ nSession = NULL;
+
// create the native AudioTrack object
AudioTrack* lpTrack = new AudioTrack();
if (lpTrack == NULL) {
@@ -288,7 +290,7 @@
0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
0,// shared mem
true,// thread can call Java
- nSession[0]);// audio session ID
+ sessionId);// audio session ID
} else if (memoryMode == javaAudioTrackFields.MODE_STATIC) {
// AudioTrack is using shared memory
@@ -309,7 +311,7 @@
0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
lpJniStorage->mMemBase,// shared mem
true,// thread can call Java
- nSession[0]);// audio session ID
+ sessionId);// audio session ID
}
if (lpTrack->initCheck() != NO_ERROR) {
@@ -317,9 +319,13 @@
goto native_init_failure;
}
+ nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
+ if (nSession == NULL) {
+ LOGE("Error creating AudioTrack: Error retrieving session id pointer");
+ goto native_init_failure;
+ }
// read the audio session ID back from AudioTrack in case we create a new session
nSession[0] = lpTrack->getSessionId();
-
env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
nSession = NULL;
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 3a85bc1..cd57ab2 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -30,6 +30,7 @@
#include <SkTemplates.h>
#include <SkXfermode.h>
+#include <OpenGLDebugRenderer.h>
#include <OpenGLRenderer.h>
#include <SkiaShader.h>
#include <SkiaColorFilter.h>
@@ -48,6 +49,8 @@
*/
#ifdef USE_OPENGL_RENDERER
+#define DEBUG_RENDERER 0
+
// ----------------------------------------------------------------------------
// Java APIs
// ----------------------------------------------------------------------------
@@ -62,7 +65,11 @@
// ----------------------------------------------------------------------------
static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject canvas) {
+#if DEBUG_RENDERER
+ return new OpenGLDebugRenderer;
+#else
return new OpenGLRenderer;
+#endif
}
static void android_view_GLES20Canvas_destroyRenderer(JNIEnv* env, jobject canvas,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 06c8423..d5065f6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -244,6 +244,14 @@
android:description="@string/permdesc_writeHistoryBookmarks"
android:protectionLevel="dangerous" />
+ <!-- Allows an application to broadcast an Intent to set an alarm for the
+ user. -->
+ <permission android:name="com.android.alarm.permission.SET_ALARM"
+ android:permissionGroup="android.permission-group.PERSONAL_INFO"
+ android:label="@string/permlab_setAlarm"
+ android:description="@string/permdesc_setAlarm"
+ android:protectionLevel="normal" />
+
<!-- ======================================= -->
<!-- Permissions for accessing location info -->
<!-- ======================================= -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2d4de8b..153dd29 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1650,6 +1650,15 @@
<!-- Title of an application permission, listed so the user can choose whether
they want to allow the application to do this. -->
+ <string name="permlab_setAlarm">set alarm in alarm clock</string>
+ <!-- Description of an application permission, listed so the user can choose whether
+ they want to allow the application to do this. -->
+ <string name="permdesc_setAlarm">Allows the application to set an alarm in
+ an installed alarm clock application. Some alarm clock applications may
+ not implement this feature.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether
+ they want to allow the application to do this. -->
<string name="permlab_writeGeolocationPermissions">Modify Browser geolocation permissions</string>
<!-- Description of an application permission, listed so the user can choose whether
they want to allow the application to do this. -->
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 9a98d53..23efcd1 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -164,15 +164,17 @@
sdensity, tdensity);
mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(),
sdensity, tdensity);
- Rect dest = mPadding;
- Rect src = mNinePatchState.mPadding;
- if (dest == src) {
- mPadding = dest = new Rect(src);
+ if (mNinePatchState.mPadding != null && mPadding != null) {
+ Rect dest = mPadding;
+ Rect src = mNinePatchState.mPadding;
+ if (dest == src) {
+ mPadding = dest = new Rect(src);
+ }
+ dest.left = Bitmap.scaleFromDensity(src.left, sdensity, tdensity);
+ dest.top = Bitmap.scaleFromDensity(src.top, sdensity, tdensity);
+ dest.right = Bitmap.scaleFromDensity(src.right, sdensity, tdensity);
+ dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity);
}
- dest.left = Bitmap.scaleFromDensity(src.left, sdensity, tdensity);
- dest.top = Bitmap.scaleFromDensity(src.top, sdensity, tdensity);
- dest.right = Bitmap.scaleFromDensity(src.right, sdensity, tdensity);
- dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity);
}
}
diff --git a/include/surfaceflinger/ISurfaceComposer.h b/include/surfaceflinger/ISurfaceComposer.h
index dd44aa5..76307b2 100644
--- a/include/surfaceflinger/ISurfaceComposer.h
+++ b/include/surfaceflinger/ISurfaceComposer.h
@@ -110,6 +110,13 @@
*/
virtual void bootFinished() = 0;
+ /* Capture the specified screen. requires READ_FRAME_BUFFER permission
+ * This function will fail if there is a secure window on screen.
+ */
+ virtual status_t captureScreen(DisplayID dpy,
+ sp<IMemoryHeap>* heap,
+ uint32_t* width, uint32_t* height, PixelFormat* format) = 0;
+
/* Signal surfaceflinger that there might be some work to do
* This is an ASYNCHRONOUS call.
*/
@@ -133,7 +140,8 @@
SET_ORIENTATION,
FREEZE_DISPLAY,
UNFREEZE_DISPLAY,
- SIGNAL
+ SIGNAL,
+ CAPTURE_SCREEN
};
virtual status_t onTransact( uint32_t code,
diff --git a/include/utils/ZipFileRO.h b/include/utils/ZipFileRO.h
index 97d31f4..9668bde 100644
--- a/include/utils/ZipFileRO.h
+++ b/include/utils/ZipFileRO.h
@@ -24,8 +24,9 @@
#ifndef __LIBS_ZIPFILERO_H
#define __LIBS_ZIPFILERO_H
-#include "Errors.h"
-#include "FileMap.h"
+#include <utils/Errors.h>
+#include <utils/FileMap.h>
+#include <utils/threads.h>
#include <stdio.h>
#include <stdlib.h>
@@ -211,6 +212,9 @@
/* open Zip archive */
int mFd;
+ /* Lock for handling the file descriptor (seeks, etc) */
+ mutable Mutex mFdLock;
+
/* zip file name */
char* mFileName;
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index a3e117f..13c58f0 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -517,12 +517,26 @@
}
if ((flags & TF_ONE_WAY) == 0) {
+ #if 0
+ if (code == 4) { // relayout
+ LOGI(">>>>>> CALLING transaction 4");
+ } else {
+ LOGI(">>>>>> CALLING transaction %d", code);
+ }
+ #endif
if (reply) {
err = waitForResponse(reply);
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
+ #if 0
+ if (code == 4) { // relayout
+ LOGI("<<<<<< RETURNING transaction 4");
+ } else {
+ LOGI("<<<<<< RETURNING transaction %d", code);
+ }
+ #endif
IF_LOG_TRANSACTIONS() {
TextOutput::Bundle _b(alog);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 0469508..e8ffd99 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -5,11 +5,13 @@
# defined in the current device/board configuration
ifeq ($(USE_OPENGL_RENDERER),true)
LOCAL_SRC_FILES:= \
+ FboCache.cpp \
FontRenderer.cpp \
GammaFontRenderer.cpp \
GradientCache.cpp \
LayerCache.cpp \
Matrix.cpp \
+ OpenGLDebugRenderer.cpp \
OpenGLRenderer.cpp \
Patch.cpp \
PatchCache.cpp \
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 5ef66f3..a4933c0 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -31,6 +31,7 @@
#include "ProgramCache.h"
#include "PathCache.h"
#include "TextDropShadowCache.h"
+#include "FboCache.h"
#include "Line.h"
namespace android {
@@ -65,6 +66,7 @@
PatchCache patchCache;
TextDropShadowCache dropShadowCache;
GammaFontRenderer fontRenderer;
+ FboCache fboCache;
Line line;
}; // class Caches
diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp
new file mode 100644
index 0000000..77fbda2
--- /dev/null
+++ b/libs/hwui/FboCache.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "FboCache.h"
+#include "Properties.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+FboCache::FboCache(): mMaxSize(DEFAULT_FBO_CACHE_SIZE) {
+ char property[PROPERTY_VALUE_MAX];
+ if (property_get(PROPERTY_FBO_CACHE_SIZE, property, NULL) > 0) {
+ LOGD(" Setting fbo cache size to %s", property);
+ mMaxSize = atoi(property);
+ } else {
+ LOGD(" Using default fbo cache size of %d", DEFAULT_FBO_CACHE_SIZE);
+ }
+}
+
+FboCache::~FboCache() {
+ clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t FboCache::getSize() {
+ return mCache.size();
+}
+
+uint32_t FboCache::getMaxSize() {
+ return mMaxSize;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void FboCache::clear() {
+
+}
+
+GLuint FboCache::get() {
+ return 0;
+}
+
+bool FboCache::put(GLuint fbo) {
+ return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FboCache.h b/libs/hwui/FboCache.h
new file mode 100644
index 0000000..66f66ea
--- /dev/null
+++ b/libs/hwui/FboCache.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_FBO_CACHE_H
+#define ANDROID_UI_FBO_CACHE_H
+
+#include <GLES2/gl2.h>
+
+#include <utils/SortedVector.h>
+
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache
+///////////////////////////////////////////////////////////////////////////////
+
+class FboCache {
+public:
+ FboCache();
+ ~FboCache();
+
+ /**
+ * Returns an FBO from the cache. If no FBO is available, a new one
+ * is created. If creating a new FBO fails, 0 is returned.
+ *
+ * When an FBO is obtained from the cache, it is removed and the
+ * total number of FBOs available in the cache decreases.
+ *
+ * @return The name of the FBO, or 0 if no FBO can be obtained.
+ */
+ GLuint get();
+
+ /**
+ * Adds the specified FBO to the cache.
+ *
+ * @param fbo The FBO to add to the cache.
+ *
+ * @return True if the FBO was added, false otherwise.
+ */
+ bool put(GLuint fbo);
+
+ /**
+ * Clears the cache. This causes all FBOs to be deleted.
+ */
+ void clear();
+
+ /**
+ * Returns the current size of the cache.
+ */
+ uint32_t getSize();
+
+ /**
+ * Returns the maximum number of FBOs that the cache can hold.
+ */
+ uint32_t getMaxSize();
+
+private:
+ SortedVector<GLuint> mCache;
+ uint32_t mMaxSize;
+}; // class FboCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_FBO_CACHE_H
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 2959814..b66696d 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -19,6 +19,7 @@
#include <SkUtils.h>
#include <cutils/properties.h>
+
#include <utils/Log.h>
#include "FontRenderer.h"
diff --git a/libs/hwui/OpenGLDebugRenderer.cpp b/libs/hwui/OpenGLDebugRenderer.cpp
new file mode 100644
index 0000000..f5a4286
--- /dev/null
+++ b/libs/hwui/OpenGLDebugRenderer.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <utils/StopWatch.h>
+
+#include "OpenGLDebugRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+void OpenGLDebugRenderer::prepare() {
+ mPrimitivesCount = 0;
+ LOGD("========= Frame start =========");
+ OpenGLRenderer::prepare();
+}
+
+void OpenGLDebugRenderer::finish() {
+ LOGD("========= Frame end =========");
+ LOGD("Primitives draw count = %d", mPrimitivesCount);
+ OpenGLRenderer::finish();
+}
+
+void OpenGLDebugRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
+ mPrimitivesCount++;
+ StopWatch w("composeLayer");
+ return OpenGLRenderer::composeLayer(current, previous);
+}
+
+int OpenGLDebugRenderer::saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* p, int flags) {
+ mPrimitivesCount++;
+ StopWatch w("saveLayer");
+ return OpenGLRenderer::saveLayer(left, top, right, bottom, p, flags);
+}
+
+void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, float left, float top,
+ const SkPaint* paint) {
+ mPrimitivesCount++;
+ StopWatch w("drawBitmap");
+ OpenGLRenderer::drawBitmap(bitmap, left, top, paint);
+}
+
+void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix,
+ const SkPaint* paint) {
+ mPrimitivesCount++;
+ StopWatch w("drawBitmapMatrix");
+ OpenGLRenderer::drawBitmap(bitmap, matrix, paint);
+}
+
+void OpenGLDebugRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint) {
+ mPrimitivesCount++;
+ StopWatch w("drawBitmapRect");
+ OpenGLRenderer::drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom,
+ dstLeft, dstTop, dstRight, dstBottom, paint);
+}
+
+void OpenGLDebugRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
+ float left, float top, float right, float bottom, const SkPaint* paint) {
+ mPrimitivesCount++;
+ StopWatch w("drawPatch");
+ OpenGLRenderer::drawPatch(bitmap, patch, left, top, right, bottom, paint);
+}
+
+void OpenGLDebugRenderer::drawColor(int color, SkXfermode::Mode mode) {
+ mPrimitivesCount++;
+ StopWatch w("drawColor");
+ OpenGLRenderer::drawColor(color, mode);
+}
+
+void OpenGLDebugRenderer::drawRect(float left, float top, float right, float bottom,
+ const SkPaint* paint) {
+ mPrimitivesCount++;
+ StopWatch w("drawRect");
+ OpenGLRenderer::drawRect(left, top, right, bottom, paint);
+}
+
+void OpenGLDebugRenderer::drawPath(SkPath* path, SkPaint* paint) {
+ mPrimitivesCount++;
+ StopWatch w("drawPath");
+ OpenGLRenderer::drawPath(path, paint);
+}
+
+void OpenGLDebugRenderer::drawLines(float* points, int count, const SkPaint* paint) {
+ mPrimitivesCount++;
+ StopWatch w("drawLines");
+ OpenGLRenderer::drawLines(points, count, paint);
+}
+
+void OpenGLDebugRenderer::drawText(const char* text, int bytesCount, int count, float x, float y,
+ SkPaint* paint) {
+ mPrimitivesCount++;
+ StopWatch w("drawText");
+ OpenGLRenderer::drawText(text, bytesCount, count, x, y, paint);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/OpenGLDebugRenderer.h b/libs/hwui/OpenGLDebugRenderer.h
new file mode 100644
index 0000000..37fac93
--- /dev/null
+++ b/libs/hwui/OpenGLDebugRenderer.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_OPENGL_DEBUG_RENDERER_H
+#define ANDROID_UI_OPENGL_DEBUG_RENDERER_H
+
+#include "OpenGLRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Renderer
+///////////////////////////////////////////////////////////////////////////////
+
+class OpenGLDebugRenderer: public OpenGLRenderer {
+public:
+ OpenGLDebugRenderer(): mPrimitivesCount(0) {
+ }
+
+ ~OpenGLDebugRenderer() {
+ }
+
+ void prepare();
+ void finish();
+
+ int saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* p, int flags);
+
+ void drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint);
+ void drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint);
+ void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint);
+ void drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top,
+ float right, float bottom, const SkPaint* paint);
+ void drawColor(int color, SkXfermode::Mode mode);
+ void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
+ void drawPath(SkPath* path, SkPaint* paint);
+ void drawLines(float* points, int count, const SkPaint* paint);
+ void drawText(const char* text, int bytesCount, int count, float x, float y,
+ SkPaint* paint);
+
+protected:
+ void composeLayer(sp<Snapshot> current, sp<Snapshot> previous);
+
+private:
+ uint32_t mPrimitivesCount;
+
+}; // class OpenGLDebugRenderer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_OPENGL_DEBUG_RENDERER_H
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e032085..dbd499e 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -24,6 +24,7 @@
#include <SkTypeface.h>
#include <utils/Log.h>
+#include <utils/StopWatch.h>
#include "OpenGLRenderer.h"
@@ -710,6 +711,7 @@
if (text == NULL || count == 0 || (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
return;
}
+
paint->setAntiAlias(true);
float length = -1.0f;
@@ -739,6 +741,7 @@
FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(paint);
fontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()),
paint->getTextSize());
+
if (mHasShadow) {
glActiveTexture(gTextureUnits[0]);
mCaches.dropShadowCache.setFontRenderer(fontRenderer);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index f903505..af2a70b 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -62,11 +62,13 @@
class OpenGLRenderer {
public:
OpenGLRenderer();
- ~OpenGLRenderer();
+ virtual ~OpenGLRenderer();
void setViewport(int width, int height);
- void prepare();
- void finish();
+
+ virtual void prepare();
+ virtual void finish();
+
void acquireContext();
void releaseContext();
@@ -75,8 +77,10 @@
void restore();
void restoreToCount(int saveCount);
- int saveLayer(float left, float top, float right, float bottom, const SkPaint* p, int flags);
- int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, int flags);
+ virtual int saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* p, int flags);
+ virtual int saveLayerAlpha(float left, float top, float right, float bottom,
+ int alpha, int flags);
void translate(float dx, float dy);
void rotate(float degrees);
@@ -90,16 +94,19 @@
bool quickReject(float left, float top, float right, float bottom);
bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
- void drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint);
- void drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint);
- void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom,
- float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint);
- void drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top,
+ virtual void drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint);
+ virtual void drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint);
+ virtual void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint);
+ virtual void drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top,
float right, float bottom, const SkPaint* paint);
- void drawColor(int color, SkXfermode::Mode mode);
- void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
- void drawPath(SkPath* path, SkPaint* paint);
- void drawLines(float* points, int count, const SkPaint* paint);
+ virtual void drawColor(int color, SkXfermode::Mode mode);
+ virtual void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
+ virtual void drawPath(SkPath* path, SkPaint* paint);
+ virtual void drawLines(float* points, int count, const SkPaint* paint);
+ virtual void drawText(const char* text, int bytesCount, int count, float x, float y,
+ SkPaint* paint);
void resetShader();
void setupShader(SkiaShader* shader);
@@ -110,7 +117,17 @@
void resetShadow();
void setupShadow(float radius, float dx, float dy, int color);
- void drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint);
+protected:
+ /**
+ * Compose the layer defined in the current snapshot with the layer
+ * defined by the previous snapshot.
+ *
+ * The current snapshot *must* be a layer (flag kFlagIsLayer set.)
+ *
+ * @param curent The current snapshot containing the layer to compose
+ * @param previous The previous snapshot to compose the current layer with
+ */
+ virtual void composeLayer(sp<Snapshot> current, sp<Snapshot> previous);
private:
/**
@@ -138,17 +155,6 @@
void setScissorFromClip();
/**
- * Compose the layer defined in the current snapshot with the layer
- * defined by the previous snapshot.
- *
- * The current snapshot *must* be a layer (flag kFlagIsLayer set.)
- *
- * @param curent The current snapshot containing the layer to compose
- * @param previous The previous snapshot to compose the current layer with
- */
- void composeLayer(sp<Snapshot> current, sp<Snapshot> previous);
-
- /**
* Creates a new layer stored in the specified snapshot.
*
* @param snapshot The snapshot associated with the new layer
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 4e2f091..d573805 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -30,6 +30,7 @@
#define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
#define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
#define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size"
+#define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size"
// These properties are defined in pixels
#define PROPERTY_TEXT_CACHE_WIDTH "ro.hwui.text_cache_width"
@@ -43,12 +44,13 @@
// Converts a number of mega-bytes into bytes
#define MB(s) s * 1024 * 1024
-#define DEFAULT_TEXTURE_CACHE_SIZE 20.0f
-#define DEFAULT_LAYER_CACHE_SIZE 6.0f
-#define DEFAULT_PATH_CACHE_SIZE 6.0f
+#define DEFAULT_TEXTURE_CACHE_SIZE 18.0f
+#define DEFAULT_LAYER_CACHE_SIZE 4.0f
+#define DEFAULT_PATH_CACHE_SIZE 5.0f
#define DEFAULT_PATCH_CACHE_SIZE 100
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
+#define DEFAULT_FBO_CACHE_SIZE 25
#define DEFAULT_TEXT_GAMMA 1.4f
#define DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD 64
diff --git a/libs/surfaceflinger_client/ISurfaceComposer.cpp b/libs/surfaceflinger_client/ISurfaceComposer.cpp
index 5c111f6..040060e 100644
--- a/libs/surfaceflinger_client/ISurfaceComposer.cpp
+++ b/libs/surfaceflinger_client/ISurfaceComposer.cpp
@@ -124,6 +124,21 @@
remote()->transact(BnSurfaceComposer::BOOT_FINISHED, data, &reply);
}
+ virtual status_t captureScreen(DisplayID dpy,
+ sp<IMemoryHeap>* heap,
+ uint32_t* width, uint32_t* height, PixelFormat* format)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+ data.writeInt32(dpy);
+ remote()->transact(BnSurfaceComposer::CAPTURE_SCREEN, data, &reply);
+ *heap = interface_cast<IMemoryHeap>(reply.readStrongBinder());
+ *width = reply.readInt32();
+ *height = reply.readInt32();
+ *format = reply.readInt32();
+ return reply.readInt32();
+ }
+
virtual void signal() const
{
Parcel data, reply;
@@ -190,6 +205,19 @@
sp<IBinder> b = getCblk()->asBinder();
reply->writeStrongBinder(b);
} break;
+ case CAPTURE_SCREEN: {
+ CHECK_INTERFACE(ISurfaceComposer, data, reply);
+ DisplayID dpy = data.readInt32();
+ sp<IMemoryHeap> heap;
+ uint32_t w, h;
+ PixelFormat f;
+ status_t res = captureScreen(dpy, &heap, &w, &h, &f);
+ reply->writeStrongBinder(heap->asBinder());
+ reply->writeInt32(w);
+ reply->writeInt32(h);
+ reply->writeInt32(f);
+ reply->writeInt32(res);
+ } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp
index 2d53136..9fcae72 100644
--- a/libs/utils/ZipFileRO.cpp
+++ b/libs/utils/ZipFileRO.cpp
@@ -22,6 +22,7 @@
#include <utils/ZipFileRO.h>
#include <utils/Log.h>
#include <utils/misc.h>
+#include <utils/threads.h>
#include <zlib.h>
@@ -195,7 +196,7 @@
free(scanBuf);
return false;
} else if (header != kLFHSignature) {
- LOGV("Not a Zip archive (found 0x%08x)\n", val);
+ LOGV("Not a Zip archive (found 0x%08x)\n", header);
free(scanBuf);
return false;
}
@@ -496,15 +497,21 @@
}
unsigned char lfhBuf[kLFHLen];
- if (lseek(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
- LOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
- return false;
- }
- ssize_t actual =
- TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
- if (actual != sizeof(lfhBuf)) {
- LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
- return false;
+
+ {
+ AutoMutex _l(mFdLock);
+
+ if (lseek(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
+ LOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
+ return false;
+ }
+
+ ssize_t actual =
+ TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
+ if (actual != sizeof(lfhBuf)) {
+ LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
+ return false;
+ }
}
if (get4LE(lfhBuf) != kLFHSignature) {
@@ -636,7 +643,7 @@
memcpy(buffer, ptr, uncompLen);
} else {
if (!inflateBuffer(buffer, ptr, uncompLen, compLen))
- goto bail;
+ goto unmap;
}
if (compLen > kSequentialMin)
@@ -644,6 +651,8 @@
result = true;
+unmap:
+ file->release();
bail:
return result;
}
@@ -667,7 +676,7 @@
getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
- const FileMap* file = createEntryFileMap(entry);
+ FileMap* file = createEntryFileMap(entry);
if (file == NULL) {
goto bail;
}
@@ -678,21 +687,23 @@
ssize_t actual = write(fd, ptr, uncompLen);
if (actual < 0) {
LOGE("Write failed: %s\n", strerror(errno));
- goto bail;
+ goto unmap;
} else if ((size_t) actual != uncompLen) {
LOGE("Partial write during uncompress (%zd of %zd)\n",
(size_t)actual, (size_t)uncompLen);
- goto bail;
+ goto unmap;
} else {
LOGI("+++ successful write\n");
}
} else {
if (!inflateBuffer(fd, ptr, uncompLen, compLen))
- goto bail;
+ goto unmap;
}
result = true;
+unmap:
+ file->release();
bail:
return result;
}
diff --git a/media/java/android/media/videoeditor/MediaImageItem.java b/media/java/android/media/videoeditor/MediaImageItem.java
index db7585a..9c39b20 100755
--- a/media/java/android/media/videoeditor/MediaImageItem.java
+++ b/media/java/android/media/videoeditor/MediaImageItem.java
@@ -116,10 +116,30 @@
}
/**
+ * This method will adjust the duration of bounding transitions if the
+ * current duration of the transactions become greater than the maximum
+ * allowable duration.
+ *
* @param durationMs The duration of the image in the storyboard timeline
*/
public void setDuration(long durationMs) {
mDurationMs = durationMs;
+
+ // Check if the duration of transitions need to be adjusted
+ if (mBeginTransition != null) {
+ final long maxDurationMs = mBeginTransition.getMaximumDuration();
+ if (mBeginTransition.getDuration() > maxDurationMs) {
+ mBeginTransition.setDuration(maxDurationMs);
+ }
+ }
+
+ if (mEndTransition != null) {
+ final long maxDurationMs = mEndTransition.getMaximumDuration();
+ if (mEndTransition.getDuration() > maxDurationMs) {
+ mEndTransition.setDuration(maxDurationMs);
+ }
+ }
+
// TODO: Validate/modify the start and the end time of effects and overlays
}
diff --git a/media/java/android/media/videoeditor/MediaItem.java b/media/java/android/media/videoeditor/MediaItem.java
index e7be35d..12fbe540 100755
--- a/media/java/android/media/videoeditor/MediaItem.java
+++ b/media/java/android/media/videoeditor/MediaItem.java
@@ -90,7 +90,7 @@
}
/**
- * @return The of the media item
+ * @return The id of the media item
*/
public String getId() {
return mUniqueId;
diff --git a/media/java/android/media/videoeditor/MediaVideoItem.java b/media/java/android/media/videoeditor/MediaVideoItem.java
index af50b83..afca55c 100755
--- a/media/java/android/media/videoeditor/MediaVideoItem.java
+++ b/media/java/android/media/videoeditor/MediaVideoItem.java
@@ -245,7 +245,10 @@
}
/**
- * Sets the start and end marks for trimming a video media item
+ * Sets the start and end marks for trimming a video media item.
+ * This method will adjust the duration of bounding transitions if the
+ * current duration of the transactions become greater than the maximum
+ * allowable duration.
*
* @param beginMs Start time in milliseconds. Set to 0 to extract from the
* beginning
@@ -265,18 +268,35 @@
}
if (beginMs != mBeginBoundaryTimeMs) {
- mBeginBoundaryTimeMs = beginMs;
if (mBeginTransition != null) {
mBeginTransition.invalidate();
}
}
- if (endMs == mEndBoundaryTimeMs) {
- mEndBoundaryTimeMs = endMs;
+ if (endMs != mEndBoundaryTimeMs) {
if (mEndTransition != null) {
mEndTransition.invalidate();
}
}
+
+ mBeginBoundaryTimeMs = beginMs;
+ mEndBoundaryTimeMs = endMs;
+
+ // Check if the duration of transitions need to be adjusted
+ if (mBeginTransition != null) {
+ final long maxDurationMs = mBeginTransition.getMaximumDuration();
+ if (mBeginTransition.getDuration() > maxDurationMs) {
+ mBeginTransition.setDuration(maxDurationMs);
+ }
+ }
+
+ if (mEndTransition != null) {
+ final long maxDurationMs = mEndTransition.getMaximumDuration();
+ if (mEndTransition.getDuration() > maxDurationMs) {
+ mEndTransition.setDuration(maxDurationMs);
+ }
+ }
+
// TODO: Validate/modify the start and the end time of effects and overlays
}
diff --git a/media/java/android/media/videoeditor/Transition.java b/media/java/android/media/videoeditor/Transition.java
index eb71285..e972aeb 100755
--- a/media/java/android/media/videoeditor/Transition.java
+++ b/media/java/android/media/videoeditor/Transition.java
@@ -124,7 +124,12 @@
* @param durationMs the duration of the transition in milliseconds
*/
public void setDuration(long durationMs) {
+ if (durationMs > getMaximumDuration()) {
+ throw new IllegalArgumentException("The duration is too large");
+ }
+
mDurationMs = durationMs;
+ invalidate();
}
/**
@@ -135,6 +140,22 @@
}
/**
+ * The duration of a transition cannot be greater than half of the minimum
+ * duration of the bounding media items.
+ *
+ * @return The maximum duration of this transition
+ */
+ public long getMaximumDuration() {
+ if (mAfterMediaItem == null) {
+ return mBeforeMediaItem.getDuration() / 2;
+ } else if (mBeforeMediaItem == null) {
+ return mAfterMediaItem.getDuration() / 2;
+ } else {
+ return (Math.min(mAfterMediaItem.getDuration(), mBeforeMediaItem.getDuration()) / 2);
+ }
+ }
+
+ /**
* @return The behavior
*/
public int getBehavior() {
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index b16372d..cb2f0f9 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -304,14 +304,7 @@
lpJniStorage->mCallbackData.audioEffect_class,
&lpJniStorage->mCallbackData);
- if (jId) {
- nId = (jint *) env->GetPrimitiveArrayCritical(jId, NULL);
- if (nId == NULL) {
- LOGE("setup: Error retrieving id pointer");
- lStatus = AUDIOEFFECT_ERROR_BAD_VALUE;
- goto setup_failure;
- }
- } else {
+ if (jId == NULL) {
LOGE("setup: NULL java array for id pointer");
lStatus = AUDIOEFFECT_ERROR_BAD_VALUE;
goto setup_failure;
@@ -336,8 +329,13 @@
goto setup_failure;
}
+ nId = (jint *) env->GetPrimitiveArrayCritical(jId, NULL);
+ if (nId == NULL) {
+ LOGE("setup: Error retrieving id pointer");
+ lStatus = AUDIOEFFECT_ERROR_BAD_VALUE;
+ goto setup_failure;
+ }
nId[0] = lpAudioEffect->id();
-
env->ReleasePrimitiveArrayCritical(jId, nId, 0);
nId = NULL;
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index 7b271ce..57cafd4 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -246,14 +246,7 @@
lpJniStorage->mCallbackData.visualizer_class,
&lpJniStorage->mCallbackData);
- if (jId) {
- nId = (jint *) env->GetPrimitiveArrayCritical(jId, NULL);
- if (nId == NULL) {
- LOGE("setup: Error retrieving id pointer");
- lStatus = VISUALIZER_ERROR_BAD_VALUE;
- goto setup_failure;
- }
- } else {
+ if (jId == NULL) {
LOGE("setup: NULL java array for id pointer");
lStatus = VISUALIZER_ERROR_BAD_VALUE;
goto setup_failure;
@@ -275,8 +268,13 @@
goto setup_failure;
}
+ nId = (jint *) env->GetPrimitiveArrayCritical(jId, NULL);
+ if (nId == NULL) {
+ LOGE("setup: Error retrieving id pointer");
+ lStatus = VISUALIZER_ERROR_BAD_VALUE;
+ goto setup_failure;
+ }
nId[0] = lpVisualizer->id();
-
env->ReleasePrimitiveArrayCritical(jId, nId, 0);
nId = NULL;
@@ -424,7 +422,6 @@
jint status = translateError(lpVisualizer->getWaveForm((uint8_t *)nWaveform));
env->ReleasePrimitiveArrayCritical(jWaveform, nWaveform, 0);
-
return status;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ade93da..6e2bfdb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -327,10 +327,7 @@
try {
final String value = c.moveToNext() ? c.getString(0) : null;
if (value == null) {
- final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
- String serial = SystemProperties.get("ro.serialno", "");
- random.setSeed(
- (serial + System.nanoTime() + new SecureRandom().nextLong()).getBytes());
+ final SecureRandom random = new SecureRandom();
final String newAndroidIdValue = Long.toHexString(random.nextLong());
Log.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue + "]");
final ContentValues values = new ContentValues();
@@ -342,8 +339,6 @@
}
}
return true;
- } catch (NoSuchAlgorithmException e) {
- return false;
} finally {
c.close();
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index aae3cff..6095117 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -114,6 +114,71 @@
private boolean mTestMode;
private static ConnectivityService sServiceInstance;
+ private static final int ENABLED = 1;
+ private static final int DISABLED = 0;
+
+ // Share the event space with NetworkStateTracker (which can't see this
+ // internal class but sends us events). If you change these, change
+ // NetworkStateTracker.java too.
+ private static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1;
+ private static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100;
+
+ /**
+ * used internally as a delayed event to make us switch back to the
+ * default network
+ */
+ private static final int EVENT_RESTORE_DEFAULT_NETWORK =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 1;
+
+ /**
+ * used internally to change our mobile data enabled flag
+ */
+ private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 2;
+
+ /**
+ * used internally to change our network preference setting
+ * arg1 = networkType to prefer
+ */
+ private static final int EVENT_SET_NETWORK_PREFERENCE =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 3;
+
+ /**
+ * used internally to synchronize inet condition reports
+ * arg1 = networkType
+ * arg2 = condition (0 bad, 100 good)
+ */
+ private static final int EVENT_INET_CONDITION_CHANGE =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 4;
+
+ /**
+ * used internally to mark the end of inet condition hold periods
+ * arg1 = networkType
+ */
+ private static final int EVENT_INET_CONDITION_HOLD_END =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 5;
+
+ /**
+ * used internally to set the background data preference
+ * arg1 = TRUE for enabled, FALSE for disabled
+ */
+ private static final int EVENT_SET_BACKGROUND_DATA =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 6;
+
+ /**
+ * used internally to set enable/disable cellular data
+ * arg1 = ENBALED or DISABLED
+ */
+ private static final int EVENT_SET_MOBILE_DATA =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 7;
+
+ /**
+ * used internally to clear a wakelock when transitioning
+ * from one net to another
+ */
+ private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 8;
+
private Handler mHandler;
// list of DeathRecipients used to make sure features are turned off when
@@ -354,28 +419,34 @@
* Sets the preferred network.
* @param preference the new preference
*/
- public synchronized void setNetworkPreference(int preference) {
+ public void setNetworkPreference(int preference) {
enforceChangePermission();
- if (ConnectivityManager.isNetworkTypeValid(preference) &&
- mNetAttributes[preference] != null &&
- mNetAttributes[preference].isDefault()) {
- if (mNetworkPreference != preference) {
- persistNetworkPreference(preference);
- mNetworkPreference = preference;
- enforcePreference();
- }
- }
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0));
}
public int getNetworkPreference() {
enforceAccessPermission();
- return mNetworkPreference;
+ int preference;
+ synchronized(this) {
+ preference = mNetworkPreference;
+ }
+ return preference;
}
- private void persistNetworkPreference(int networkPreference) {
- final ContentResolver cr = mContext.getContentResolver();
- Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE,
- networkPreference);
+ private void handleSetNetworkPreference(int preference) {
+ if (ConnectivityManager.isNetworkTypeValid(preference) &&
+ mNetAttributes[preference] != null &&
+ mNetAttributes[preference].isDefault()) {
+ if (mNetworkPreference != preference) {
+ final ContentResolver cr = mContext.getContentResolver();
+ Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, preference);
+ synchronized(this) {
+ mNetworkPreference = preference;
+ }
+ enforcePreference();
+ }
+ }
}
private int getPersistedNetworkPreference() {
@@ -628,8 +699,7 @@
mNetRequestersPids[usedNetworkType].add(currentPid);
}
}
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK,
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK,
f), getRestoreDefaultNetworkDelay());
@@ -871,15 +941,18 @@
android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING,
"ConnectivityService");
- if (getBackgroundDataSetting() == allowBackgroundDataUsage) return;
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_BACKGROUND_DATA,
+ (allowBackgroundDataUsage ? ENABLED : DISABLED), 0));
+ }
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.BACKGROUND_DATA,
- allowBackgroundDataUsage ? 1 : 0);
-
- Intent broadcast = new Intent(
- ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
- mContext.sendBroadcast(broadcast);
+ private void handleSetBackgroundData(boolean enabled) {
+ if (enabled != getBackgroundDataSetting()) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.BACKGROUND_DATA, enabled ? 1 : 0);
+ Intent broadcast = new Intent(
+ ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
+ mContext.sendBroadcast(broadcast);
+ }
}
/**
@@ -896,10 +969,15 @@
/**
* @see ConnectivityManager#setMobileDataEnabled(boolean)
*/
- public synchronized void setMobileDataEnabled(boolean enabled) {
+ public void setMobileDataEnabled(boolean enabled) {
enforceChangePermission();
if (DBG) Slog.d(TAG, "setMobileDataEnabled(" + enabled + ")");
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA,
+ (enabled ? ENABLED : DISABLED), 0));
+ }
+
+ private void handleSetMobileData(boolean enabled) {
if (getMobileDataEnabled() == enabled) return;
Settings.Secure.putInt(mContext.getContentResolver(),
@@ -907,7 +985,9 @@
if (enabled) {
if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
- if (DBG) Slog.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]);
+ if (DBG) {
+ Slog.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]);
+ }
mNetTrackers[ConnectivityManager.TYPE_MOBILE].reconnect();
}
} else {
@@ -1267,7 +1347,7 @@
// new network
if (mNetTransitionWakeLock.isHeld()) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(
- NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
mNetTransitionWakeLockSerialNumber, 0),
1000);
}
@@ -1705,11 +1785,7 @@
type = info.getType();
handleDnsConfigurationChange(type);
break;
- case NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK:
- FeatureUser u = (FeatureUser)msg.obj;
- u.expire();
- break;
- case NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK:
+ case EVENT_CLEAR_NET_TRANSITION_WAKELOCK:
String causedBy = null;
synchronized (ConnectivityService.this) {
if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
@@ -1723,70 +1799,42 @@
causedBy + " released by timeout");
}
break;
- case NetworkStateTracker.EVENT_INET_CONDITION_CHANGE:
- if (DBG) {
- Slog.d(TAG, "Inet connectivity change, net=" +
- msg.arg1 + ", condition=" + msg.arg2 +
- ",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
- }
- if (mActiveDefaultNetwork == -1) {
- if (DBG) Slog.d(TAG, "no active default network - aborting");
- break;
- }
- if (mActiveDefaultNetwork != msg.arg1) {
- if (DBG) Slog.d(TAG, "given net not default - aborting");
- break;
- }
- mDefaultInetCondition = msg.arg2;
- int delay;
- if (mInetConditionChangeInFlight == false) {
- if (DBG) Slog.d(TAG, "starting a change hold");
- // setup a new hold to debounce this
- if (mDefaultInetCondition > 50) {
- delay = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
- } else {
- delay = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000);
- }
- mInetConditionChangeInFlight = true;
- sendMessageDelayed(obtainMessage(
- NetworkStateTracker.EVENT_INET_CONDITION_HOLD_END,
- mActiveDefaultNetwork, mDefaultConnectionSequence), delay);
- } else {
- // we've set the new condition, when this hold ends that will get
- // picked up
- if (DBG) Slog.d(TAG, "currently in hold - not setting new end evt");
- }
+ case EVENT_RESTORE_DEFAULT_NETWORK:
+ FeatureUser u = (FeatureUser)msg.obj;
+ u.expire();
break;
- case NetworkStateTracker.EVENT_INET_CONDITION_HOLD_END:
- if (DBG) {
- Slog.d(TAG, "Inet hold end, net=" + msg.arg1 +
- ", condition =" + mDefaultInetCondition +
- ", published condition =" + mDefaultInetConditionPublished);
- }
- mInetConditionChangeInFlight = false;
-
- if (mActiveDefaultNetwork == -1) {
- if (DBG) Slog.d(TAG, "no active default network - aborting");
- break;
- }
- if (mDefaultConnectionSequence != msg.arg2) {
- if (DBG) Slog.d(TAG, "event hold for obsolete network - aborting");
- break;
- }
- if (mDefaultInetConditionPublished == mDefaultInetCondition) {
- if (DBG) Slog.d(TAG, "no change in condition - aborting");
- break;
- }
- NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
- if (networkInfo.isConnected() == false) {
- if (DBG) Slog.d(TAG, "default network not connected - aborting");
- break;
- }
- mDefaultInetConditionPublished = mDefaultInetCondition;
- sendInetConditionBroadcast(networkInfo);
+ case EVENT_INET_CONDITION_CHANGE:
+ {
+ int netType = msg.arg1;
+ int condition = msg.arg2;
+ handleInetConditionChange(netType, condition);
break;
+ }
+ case EVENT_INET_CONDITION_HOLD_END:
+ {
+ int netType = msg.arg1;
+ int sequence = msg.arg2;
+ handleInetConditionHoldEnd(netType, sequence);
+ break;
+ }
+ case EVENT_SET_NETWORK_PREFERENCE:
+ {
+ int preference = msg.arg1;
+ handleSetNetworkPreference(preference);
+ break;
+ }
+ case EVENT_SET_BACKGROUND_DATA:
+ {
+ boolean enabled = (msg.arg1 == ENABLED);
+ handleSetBackgroundData(enabled);
+ break;
+ }
+ case EVENT_SET_MOBILE_DATA:
+ {
+ boolean enabled = (msg.arg1 == ENABLED);
+ handleSetMobileData(enabled);
+ break;
+ }
}
}
}
@@ -1893,7 +1941,7 @@
mNetTransitionWakeLockCausedBy = forWhom;
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(
- NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
mNetTransitionWakeLockSerialNumber, 0),
mNetTransitionWakeLockTimeout);
return;
@@ -1918,6 +1966,72 @@
}
}
mHandler.sendMessage(mHandler.obtainMessage(
- NetworkStateTracker.EVENT_INET_CONDITION_CHANGE, networkType, percentage));
+ EVENT_INET_CONDITION_CHANGE, networkType, percentage));
+ }
+
+ private void handleInetConditionChange(int netType, int condition) {
+ if (DBG) {
+ Slog.d(TAG, "Inet connectivity change, net=" +
+ netType + ", condition=" + condition +
+ ",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
+ }
+ if (mActiveDefaultNetwork == -1) {
+ if (DBG) Slog.d(TAG, "no active default network - aborting");
+ return;
+ }
+ if (mActiveDefaultNetwork != netType) {
+ if (DBG) Slog.d(TAG, "given net not default - aborting");
+ return;
+ }
+ mDefaultInetCondition = condition;
+ int delay;
+ if (mInetConditionChangeInFlight == false) {
+ if (DBG) Slog.d(TAG, "starting a change hold");
+ // setup a new hold to debounce this
+ if (mDefaultInetCondition > 50) {
+ delay = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
+ } else {
+ delay = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000);
+ }
+ mInetConditionChangeInFlight = true;
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_INET_CONDITION_HOLD_END,
+ mActiveDefaultNetwork, mDefaultConnectionSequence), delay);
+ } else {
+ // we've set the new condition, when this hold ends that will get
+ // picked up
+ if (DBG) Slog.d(TAG, "currently in hold - not setting new end evt");
+ }
+ }
+
+ private void handleInetConditionHoldEnd(int netType, int sequence) {
+ if (DBG) {
+ Slog.d(TAG, "Inet hold end, net=" + netType +
+ ", condition =" + mDefaultInetCondition +
+ ", published condition =" + mDefaultInetConditionPublished);
+ }
+ mInetConditionChangeInFlight = false;
+
+ if (mActiveDefaultNetwork == -1) {
+ if (DBG) Slog.d(TAG, "no active default network - aborting");
+ return;
+ }
+ if (mDefaultConnectionSequence != sequence) {
+ if (DBG) Slog.d(TAG, "event hold for obsolete network - aborting");
+ return;
+ }
+ if (mDefaultInetConditionPublished == mDefaultInetCondition) {
+ if (DBG) Slog.d(TAG, "no change in condition - aborting");
+ return;
+ }
+ NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
+ if (networkInfo.isConnected() == false) {
+ if (DBG) Slog.d(TAG, "default network not connected - aborting");
+ return;
+ }
+ mDefaultInetConditionPublished = mDefaultInetCondition;
+ sendInetConditionBroadcast(networkInfo);
+ return;
}
}
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 3e4f522..7100cc5 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -5687,9 +5687,12 @@
int requestedWidth, int requestedHeight, int viewFlags,
boolean insetsPending, Rect outFrame, Rect outContentInsets,
Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
- return relayoutWindow(this, window, attrs,
+ //Log.d(TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid());
+ int res = relayoutWindow(this, window, attrs,
requestedWidth, requestedHeight, viewFlags, insetsPending,
outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface);
+ //Log.d(TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid());
+ return res;
}
public void setTransparentRegion(IWindow window, Region region) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 8e22652..9685fb7 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -566,6 +566,11 @@
private final StringBuilder mStrictModeBuffer = new StringBuilder();
/**
+ * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
+ */
+ private boolean mPendingBroadcastTimeoutMessage;
+
+ /**
* Intent broadcast that we have tried to start, but are
* waiting for its application's process to be created. We only
* need one (instead of a list) because we always process broadcasts
@@ -1071,17 +1076,8 @@
processNextBroadcast(true);
} break;
case BROADCAST_TIMEOUT_MSG: {
- if (mDidDexOpt) {
- mDidDexOpt = false;
- Message nmsg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG);
- mHandler.sendMessageDelayed(nmsg, BROADCAST_TIMEOUT);
- return;
- }
- // Only process broadcast timeouts if the system is ready. That way
- // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended
- // to do heavy lifting for system up
- if (mProcessesReady) {
- broadcastTimeout();
+ synchronized (ActivityManagerService.this) {
+ broadcastTimeoutLocked(true);
}
} break;
case SERVICE_TIMEOUT_MSG: {
@@ -2810,6 +2806,21 @@
}
}
+ private final class AppNotResponding implements Runnable {
+ private final ProcessRecord mApp;
+ private final String mAnnotation;
+
+ public AppNotResponding(ProcessRecord app, String annotation) {
+ mApp = app;
+ mAnnotation = annotation;
+ }
+
+ @Override
+ public void run() {
+ appNotResponding(mApp, null, null, mAnnotation);
+ }
+ }
+
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
ActivityRecord parent, final String annotation) {
ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
@@ -3678,7 +3689,7 @@
Slog.w(TAG, "Exception in new application when starting receiver "
+ br.curComponent.flattenToShortString(), e);
badApp = true;
- logBroadcastReceiverDiscard(br);
+ logBroadcastReceiverDiscardLocked(br);
finishReceiverLocked(br.receiver, br.resultCode, br.resultData,
br.resultExtras, br.resultAbort, true);
scheduleBroadcastsLocked();
@@ -6406,7 +6417,7 @@
// The current broadcast is waiting for this app's receiver
// to be finished. Looks like that's not going to happen, so
// let the broadcast continue.
- logBroadcastReceiverDiscard(r);
+ logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r.receiver, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, true);
reschedule = true;
@@ -6415,7 +6426,7 @@
if (r != null && r.curApp == app) {
if (DEBUG_BROADCAST) Slog.v(TAG,
"skip & discard pending app " + r);
- logBroadcastReceiverDiscard(r);
+ logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r.receiver, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, true);
reschedule = true;
@@ -10633,7 +10644,7 @@
Binder.restoreCallingIdentity(origId);
}
- private final void logBroadcastReceiverDiscard(BroadcastRecord r) {
+ private final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
if (r.nextReceiver > 0) {
Object curReceiver = r.receivers.get(r.nextReceiver-1);
if (curReceiver instanceof BroadcastFilter) {
@@ -10661,67 +10672,108 @@
}
}
- private final void broadcastTimeout() {
+ private final void setBroadcastTimeoutLocked(long timeoutTime) {
+ if (! mPendingBroadcastTimeoutMessage) {
+ Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG);
+ mHandler.sendMessageAtTime(msg, timeoutTime);
+ mPendingBroadcastTimeoutMessage = true;
+ }
+ }
+
+ private final void cancelBroadcastTimeoutLocked() {
+ if (mPendingBroadcastTimeoutMessage) {
+ mHandler.removeMessages(BROADCAST_TIMEOUT_MSG);
+ mPendingBroadcastTimeoutMessage = false;
+ }
+ }
+
+ private final void broadcastTimeoutLocked(boolean fromMsg) {
+ if (fromMsg) {
+ mPendingBroadcastTimeoutMessage = false;
+ }
+
+ if (mOrderedBroadcasts.size() == 0) {
+ return;
+ }
+
+ long now = SystemClock.uptimeMillis();
+ BroadcastRecord r = mOrderedBroadcasts.get(0);
+ if (fromMsg) {
+ if (mDidDexOpt) {
+ // Delay timeouts until dexopt finishes.
+ mDidDexOpt = false;
+ long timeoutTime = SystemClock.uptimeMillis() + BROADCAST_TIMEOUT;
+ setBroadcastTimeoutLocked(timeoutTime);
+ return;
+ }
+ if (! mProcessesReady) {
+ // Only process broadcast timeouts if the system is ready. That way
+ // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended
+ // to do heavy lifting for system up.
+ return;
+ }
+
+ long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT;
+ if (timeoutTime > now) {
+ // We can observe premature timeouts because we do not cancel and reset the
+ // broadcast timeout message after each receiver finishes. Instead, we set up
+ // an initial timeout then kick it down the road a little further as needed
+ // when it expires.
+ if (DEBUG_BROADCAST) Slog.v(TAG,
+ "Premature timeout @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
+ + timeoutTime);
+ setBroadcastTimeoutLocked(timeoutTime);
+ return;
+ }
+ }
+
+ Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver
+ + ", started " + (now - r.receiverTime) + "ms ago");
+ r.receiverTime = now;
+ r.anrCount++;
+
+ // Current receiver has passed its expiration date.
+ if (r.nextReceiver <= 0) {
+ Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0");
+ return;
+ }
+
ProcessRecord app = null;
String anrMessage = null;
- synchronized (this) {
- if (mOrderedBroadcasts.size() == 0) {
- return;
- }
- long now = SystemClock.uptimeMillis();
- BroadcastRecord r = mOrderedBroadcasts.get(0);
- if ((r.receiverTime+BROADCAST_TIMEOUT) > now) {
- if (DEBUG_BROADCAST) Slog.v(TAG,
- "Premature timeout @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
- + (r.receiverTime + BROADCAST_TIMEOUT));
- Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG);
- mHandler.sendMessageAtTime(msg, r.receiverTime+BROADCAST_TIMEOUT);
- return;
- }
-
- Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver);
- r.receiverTime = now;
- r.anrCount++;
-
- // Current receiver has passed its expiration date.
- if (r.nextReceiver <= 0) {
- Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0");
- return;
- }
-
- Object curReceiver = r.receivers.get(r.nextReceiver-1);
- Slog.w(TAG, "Receiver during timeout: " + curReceiver);
- logBroadcastReceiverDiscard(r);
- if (curReceiver instanceof BroadcastFilter) {
- BroadcastFilter bf = (BroadcastFilter)curReceiver;
- if (bf.receiverList.pid != 0
- && bf.receiverList.pid != MY_PID) {
- synchronized (this.mPidsSelfLocked) {
- app = this.mPidsSelfLocked.get(
- bf.receiverList.pid);
- }
+ Object curReceiver = r.receivers.get(r.nextReceiver-1);
+ Slog.w(TAG, "Receiver during timeout: " + curReceiver);
+ logBroadcastReceiverDiscardLocked(r);
+ if (curReceiver instanceof BroadcastFilter) {
+ BroadcastFilter bf = (BroadcastFilter)curReceiver;
+ if (bf.receiverList.pid != 0
+ && bf.receiverList.pid != MY_PID) {
+ synchronized (this.mPidsSelfLocked) {
+ app = this.mPidsSelfLocked.get(
+ bf.receiverList.pid);
}
- } else {
- app = r.curApp;
}
-
- if (app != null) {
- anrMessage = "Broadcast of " + r.intent.toString();
- }
-
- if (mPendingBroadcast == r) {
- mPendingBroadcast = null;
- }
-
- // Move on to the next receiver.
- finishReceiverLocked(r.receiver, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, true);
- scheduleBroadcastsLocked();
+ } else {
+ app = r.curApp;
}
+ if (app != null) {
+ anrMessage = "Broadcast of " + r.intent.toString();
+ }
+
+ if (mPendingBroadcast == r) {
+ mPendingBroadcast = null;
+ }
+
+ // Move on to the next receiver.
+ finishReceiverLocked(r.receiver, r.resultCode, r.resultData,
+ r.resultExtras, r.resultAbort, true);
+ scheduleBroadcastsLocked();
+
if (anrMessage != null) {
- appNotResponding(app, null, null, anrMessage);
+ // Post the ANR to the handler since we do not want to process ANRs while
+ // potentially holding our lock.
+ mHandler.post(new AppNotResponding(app, anrMessage));
}
}
@@ -10763,9 +10815,10 @@
}
- static void performReceive(ProcessRecord app, IIntentReceiver receiver,
+ static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
Intent intent, int resultCode, String data, Bundle extras,
boolean ordered, boolean sticky) throws RemoteException {
+ // Send the intent to the receiver asynchronously using one-way binder calls.
if (app != null && app.thread != null) {
// If we have an app thread, do the call through that so it is
// correctly ordered with other one-way calls.
@@ -10776,7 +10829,7 @@
}
}
- private final void deliverToRegisteredReceiver(BroadcastRecord r,
+ private final void deliverToRegisteredReceiverLocked(BroadcastRecord r,
BroadcastFilter filter, boolean ordered) {
boolean skip = false;
if (filter.requiredPermission != null) {
@@ -10834,7 +10887,7 @@
Slog.i(TAG, "Delivering to " + filter
+ " (seq=" + seq + "): " + r);
}
- performReceive(filter.receiverList.app, filter.receiverList.receiver,
+ performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
new Intent(r.intent), r.resultCode,
r.resultData, r.resultExtras, r.ordered, r.initialSticky);
if (ordered) {
@@ -10891,7 +10944,7 @@
if (DEBUG_BROADCAST) Slog.v(TAG,
"Delivering non-ordered to registered "
+ target + ": " + r);
- deliverToRegisteredReceiver(r, (BroadcastFilter)target, false);
+ deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
}
addBroadcastToHistoryLocked(r);
if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast "
@@ -10947,7 +11000,7 @@
// and continue to make progress.
//
// This is only done if the system is ready so that PRE_BOOT_COMPLETED
- // receivers don't get executed with with timeouts. They're intended for
+ // receivers don't get executed with timeouts. They're intended for
// one time heavy lifting after system upgrades and can take
// significant amounts of time.
int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
@@ -10963,7 +11016,7 @@
+ " numReceivers=" + numReceivers
+ " nextReceiver=" + r.nextReceiver
+ " state=" + r.state);
- broadcastTimeout(); // forcibly finish this broadcast
+ broadcastTimeoutLocked(false); // forcibly finish this broadcast
forceReceive = true;
r.state = BroadcastRecord.IDLE;
}
@@ -10987,7 +11040,7 @@
Slog.i(TAG, "Finishing broadcast " + r.intent.getAction()
+ " seq=" + seq + " app=" + r.callerApp);
}
- performReceive(r.callerApp, r.resultTo,
+ performReceiveLocked(r.callerApp, r.resultTo,
new Intent(r.intent), r.resultCode,
r.resultData, r.resultExtras, false, false);
} catch (RemoteException e) {
@@ -10996,7 +11049,7 @@
}
if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG");
- mHandler.removeMessages(BROADCAST_TIMEOUT_MSG);
+ cancelBroadcastTimeoutLocked();
if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast "
+ r);
@@ -11021,11 +11074,12 @@
if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast "
+ r);
+ }
+ if (! mPendingBroadcastTimeoutMessage) {
+ long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT;
if (DEBUG_BROADCAST) Slog.v(TAG,
- "Submitting BROADCAST_TIMEOUT_MSG for " + r + " at "
- + (r.receiverTime + BROADCAST_TIMEOUT));
- Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG);
- mHandler.sendMessageAtTime(msg, r.receiverTime+BROADCAST_TIMEOUT);
+ "Submitting BROADCAST_TIMEOUT_MSG for " + r + " at " + timeoutTime);
+ setBroadcastTimeoutLocked(timeoutTime);
}
Object nextReceiver = r.receivers.get(recIdx);
@@ -11036,7 +11090,7 @@
if (DEBUG_BROADCAST) Slog.v(TAG,
"Delivering ordered to registered "
+ filter + ": " + r);
- deliverToRegisteredReceiver(r, filter, r.ordered);
+ deliverToRegisteredReceiverLocked(r, filter, r.ordered);
if (r.receiver == null || !r.ordered) {
// The receiver has already finished, so schedule to
// process the next one.
@@ -11144,7 +11198,7 @@
+ info.activityInfo.applicationInfo.packageName + "/"
+ info.activityInfo.applicationInfo.uid + " for broadcast "
+ r.intent + ": process is bad");
- logBroadcastReceiverDiscard(r);
+ logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r.receiver, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, true);
scheduleBroadcastsLocked();
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
index 66a2c05..fa3f64a 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -84,7 +84,7 @@
private static final String ANONYMOUS = "anonymous";
private static final String SERVER_ERROR_PREFIX = "Response: ";
private static final int EXPIRY_TIME = 3600; // in seconds
- private static final int CANCEL_CALL_TIMER = 5; // in seconds
+ private static final int CANCEL_CALL_TIMER = 3; // in seconds
private static final EventObject DEREGISTER = new EventObject("Deregister");
private static final EventObject END_CALL = new EventObject("End call");
diff --git a/services/surfaceflinger/GLExtensions.cpp b/services/surfaceflinger/GLExtensions.cpp
index 850866a..493122d 100644
--- a/services/surfaceflinger/GLExtensions.cpp
+++ b/services/surfaceflinger/GLExtensions.cpp
@@ -92,6 +92,10 @@
// hack for Adreno 200
mHaveTextureExternal = true;
}
+
+ if (hasExtension("GL_OES_framebuffer_object")) {
+ mHaveFramebufferObject = true;
+ }
}
bool GLExtensions::hasExtension(char const* extension) const
diff --git a/services/surfaceflinger/GLExtensions.h b/services/surfaceflinger/GLExtensions.h
index bbb284e..c86c66a 100644
--- a/services/surfaceflinger/GLExtensions.h
+++ b/services/surfaceflinger/GLExtensions.h
@@ -39,6 +39,7 @@
bool mHaveTextureExternal : 1;
bool mHaveNpot : 1;
bool mHaveDirectTexture : 1;
+ bool mHaveFramebufferObject : 1;
String8 mVendor;
String8 mRenderer;
@@ -66,6 +67,10 @@
return mHaveDirectTexture;
}
+ inline bool haveFramebufferObject() const {
+ return mHaveFramebufferObject;
+ }
+
void initWithGLStrings(
GLubyte const* vendor,
GLubyte const* renderer,
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index b353bff..17b98a6 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -76,6 +76,7 @@
mBootTime(systemTime()),
mHardwareTest("android.permission.HARDWARE_TEST"),
mAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER"),
+ mReadFramebuffer("android.permission.READ_FRAME_BUFFER"),
mDump("android.permission.DUMP"),
mVisibleRegionsDirty(false),
mHwWorkListDirty(false),
@@ -1567,8 +1568,23 @@
"can't access SurfaceFlinger pid=%d, uid=%d", pid, uid);
return PERMISSION_DENIED;
}
+ break;
+ }
+ case CAPTURE_SCREEN:
+ {
+ // codes that require permission check
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int pid = ipc->getCallingPid();
+ const int uid = ipc->getCallingUid();
+ if ((uid != AID_GRAPHICS) && !mReadFramebuffer.check(pid, uid)) {
+ LOGE("Permission Denial: "
+ "can't read framebuffer pid=%d, uid=%d", pid, uid);
+ return PERMISSION_DENIED;
+ }
+ break;
}
}
+
status_t err = BnSurfaceComposer::onTransact(code, data, reply, flags);
if (err == UNKNOWN_TRANSACTION || err == PERMISSION_DENIED) {
CHECK_INTERFACE(ISurfaceComposer, data, reply);
@@ -1637,6 +1653,139 @@
// ---------------------------------------------------------------------------
+status_t SurfaceFlinger::captureScreen(DisplayID dpy,
+ sp<IMemoryHeap>* heap,
+ uint32_t* width, uint32_t* height, PixelFormat* format)
+{
+ // only one display supported for now
+ if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
+ return BAD_VALUE;
+
+ if (!GLExtensions::getInstance().haveFramebufferObject())
+ return INVALID_OPERATION;
+
+ class MessageCaptureScreen : public MessageBase {
+ SurfaceFlinger* flinger;
+ DisplayID dpy;
+ sp<IMemoryHeap>* heap;
+ uint32_t* w;
+ uint32_t* h;
+ PixelFormat* f;
+ status_t result;
+ public:
+ MessageCaptureScreen(SurfaceFlinger* flinger, DisplayID dpy,
+ sp<IMemoryHeap>* heap, uint32_t* w, uint32_t* h, PixelFormat* f)
+ : flinger(flinger), dpy(dpy),
+ heap(heap), w(w), h(h), f(f), result(PERMISSION_DENIED)
+ {
+ }
+ status_t getResult() const {
+ return result;
+ }
+ virtual bool handler() {
+ Mutex::Autolock _l(flinger->mStateLock);
+
+ // if we have secure windows, never allow the screen capture
+ if (flinger->mSecureFrameBuffer)
+ return true;
+
+ // make sure to clear all GL error flags
+ while ( glGetError() != GL_NO_ERROR ) ;
+
+ // get screen geometry
+ const DisplayHardware& hw(flinger->graphicPlane(dpy).displayHardware());
+ const uint32_t sw = hw.getWidth();
+ const uint32_t sh = hw.getHeight();
+ const Region screenBounds(hw.bounds());
+ const size_t size = sw * sh * 4;
+
+ // create a FBO
+ GLuint name, tname;
+ glGenRenderbuffersOES(1, &tname);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, tname);
+ glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_RGBA8_OES, sw, sh);
+ glGenFramebuffersOES(1, &name);
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, name);
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
+ GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, tname);
+
+ GLenum status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
+ if (status == GL_FRAMEBUFFER_COMPLETE_OES) {
+
+ // invert everything, b/c glReadPixel() below will invert the FB
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrthof(0, sw, 0, sh, 0, 1);
+ glMatrixMode(GL_MODELVIEW);
+
+ // redraw the screen entirely...
+ glClearColor(0,0,0,1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ const Vector< sp<LayerBase> >& layers(
+ flinger->mVisibleLayersSortedByZ);
+ const size_t count = layers.size();
+ for (size_t i=0 ; i<count ; ++i) {
+ const sp<LayerBase>& layer(layers[i]);
+ if (!strcmp(layer->getTypeId(), "LayerBuffer")) {
+ // we cannot render LayerBuffer because it doens't
+ // use OpenGL, and won't show-up in the FBO.
+ continue;
+ }
+ layer->draw(screenBounds);
+ }
+
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+
+ // check for errors and return screen capture
+ if (glGetError() != GL_NO_ERROR) {
+ // error while rendering
+ result = INVALID_OPERATION;
+ } else {
+ // allocate shared memory large enough to hold the
+ // screen capture
+ sp<MemoryHeapBase> base(
+ new MemoryHeapBase(size, 0, "screen-capture") );
+ void* const ptr = base->getBase();
+ if (ptr) {
+ // capture the screen with glReadPixels()
+ glReadPixels(0, 0, sw, sh, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
+ if (glGetError() == GL_NO_ERROR) {
+ *heap = base;
+ *w = sw;
+ *h = sh;
+ *f = PIXEL_FORMAT_RGBA_8888;
+ result = NO_ERROR;
+ }
+ } else {
+ result = NO_MEMORY;
+ }
+ }
+ } else {
+ result = BAD_VALUE;
+ }
+
+ // release FBO resources
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
+ glDeleteRenderbuffersOES(1, &tname);
+ glDeleteFramebuffersOES(1, &name);
+ return true;
+ }
+ };
+
+ sp<MessageBase> msg = new MessageCaptureScreen(this,
+ dpy, heap, width, height, format);
+ status_t res = postMessageSync(msg);
+ if (res == NO_ERROR) {
+ res = static_cast<MessageCaptureScreen*>( msg.get() )->getResult();
+ }
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
sp<Layer> SurfaceFlinger::getLayer(const sp<ISurface>& sur) const
{
sp<Layer> result;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 551e8e7..6e9ecbd 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -193,6 +193,11 @@
virtual status_t unfreezeDisplay(DisplayID dpy, uint32_t flags);
virtual int setOrientation(DisplayID dpy, int orientation, uint32_t flags);
virtual void signal() const;
+ virtual status_t captureScreen(DisplayID dpy,
+ sp<IMemoryHeap>* heap,
+ uint32_t* width,
+ uint32_t* height,
+ PixelFormat* format);
void screenReleased(DisplayID dpy);
void screenAcquired(DisplayID dpy);
@@ -362,6 +367,7 @@
nsecs_t mBootTime;
Permission mHardwareTest;
Permission mAccessSurfaceFlinger;
+ Permission mReadFramebuffer;
Permission mDump;
// Can only accessed from the main thread, these members
diff --git a/services/surfaceflinger/tests/screencap/Android.mk b/services/surfaceflinger/tests/screencap/Android.mk
new file mode 100644
index 0000000..1cfb471
--- /dev/null
+++ b/services/surfaceflinger/tests/screencap/Android.mk
@@ -0,0 +1,26 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ screencap.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ libutils \
+ libbinder \
+ libskia \
+ libui \
+ libsurfaceflinger_client
+
+LOCAL_MODULE:= test-screencap
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_C_INCLUDES += \
+ external/skia/include/core \
+ external/skia/include/effects \
+ external/skia/include/images \
+ external/skia/src/ports \
+ external/skia/include/utils
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/surfaceflinger/tests/screencap/screencap.cpp b/services/surfaceflinger/tests/screencap/screencap.cpp
new file mode 100644
index 0000000..9e893f4
--- /dev/null
+++ b/services/surfaceflinger/tests/screencap/screencap.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+
+#include <binder/IMemory.h>
+#include <surfaceflinger/ISurfaceComposer.h>
+
+#include <SkImageEncoder.h>
+#include <SkBitmap.h>
+
+using namespace android;
+
+int main(int argc, char** argv)
+{
+ if (argc != 2) {
+ printf("usage: %s path\n", argv[0]);
+ exit(0);
+ }
+
+ const String16 name("SurfaceFlinger");
+ sp<ISurfaceComposer> composer;
+ getService(name, &composer);
+
+ sp<IMemoryHeap> heap;
+ uint32_t w, h;
+ PixelFormat f;
+ status_t err = composer->captureScreen(0, &heap, &w, &h, &f);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "screen capture failed: %s\n", strerror(-err));
+ exit(0);
+ }
+
+ printf("screen capture success: w=%u, h=%u, pixels=%p\n",
+ w, h, heap->getBase());
+
+ printf("saving file as PNG in %s ...\n", argv[1]);
+
+ SkBitmap b;
+ b.setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ b.setPixels(heap->getBase());
+ SkImageEncoder::EncodeFile(argv[1], b,
+ SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality);
+
+ return 0;
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index 4791fbd..08194d4 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -73,7 +73,9 @@
public class SipPhone extends SipPhoneBase {
private static final String LOG_TAG = "SipPhone";
private static final boolean LOCAL_DEBUG = true;
- private static final int SESSION_TIMEOUT = 8; // in seconds
+ private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
+ private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
+ private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
// A call that is ringing or (call) waiting
private SipCall ringingCall = new SipCall();
@@ -693,7 +695,7 @@
void acceptCall() throws CallStateException {
try {
- mSipAudioCall.answerCall(SESSION_TIMEOUT);
+ mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL);
} catch (SipException e) {
throw new CallStateException("acceptCall(): " + e);
}
@@ -711,7 +713,7 @@
void dial() throws SipException {
setState(Call.State.DIALING);
mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
- SESSION_TIMEOUT);
+ TIMEOUT_MAKE_CALL);
mSipAudioCall.setRingbackToneEnabled(false);
mSipAudioCall.setListener(mAdapter);
}
@@ -719,7 +721,7 @@
void hold() throws CallStateException {
setState(Call.State.HOLDING);
try {
- mSipAudioCall.holdCall(SESSION_TIMEOUT);
+ mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL);
} catch (SipException e) {
throw new CallStateException("hold(): " + e);
}
@@ -729,7 +731,7 @@
mSipAudioCall.setAudioGroup(audioGroup);
setState(Call.State.ACTIVE);
try {
- mSipAudioCall.continueCall(SESSION_TIMEOUT);
+ mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL);
} catch (SipException e) {
throw new CallStateException("unhold(): " + e);
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java
new file mode 100755
index 0000000..0689c92
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/Nullable.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a parameter or field can be null.
+ * <p/>
+ * When decorating a method call parameter, this denotes the parameter can
+ * legitimately be null and the method will gracefully deal with it. Typically used
+ * on optional parameters.
+ * <p/>
+ * When decorating a method, this denotes the method might legitimately return null.
+ * <p/>
+ * This is a marker annotation and it has no specific attributes.
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface Nullable {
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java
new file mode 100755
index 0000000..e4e016b
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes that the class, method or field has its visibility relaxed so
+ * that unit tests can access it.
+ * <p/>
+ * The <code>visibility</code> argument can be used to specific what the original
+ * visibility should have been if it had not been made public or package-private for testing.
+ * The default is to consider the element private.
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface VisibleForTesting {
+ /**
+ * Intended visibility if the element had not been made public or package-private for
+ * testing.
+ */
+ enum Visibility {
+ /** The element should be considered protected. */
+ PROTECTED,
+ /** The element should be considered package-private. */
+ PACKAGE,
+ /** The element should be considered private. */
+ PRIVATE
+ }
+
+ /**
+ * Intended visibility if the element had not been made public or package-private for testing.
+ * If not specified, one should assume the element originally intended to be private.
+ */
+ Visibility visibility() default Visibility.PRIVATE;
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
index 5424efa..722dce2 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
@@ -16,6 +16,9 @@
package com.android.tools.layoutlib.create;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility;
+
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
@@ -27,13 +30,18 @@
* Indicates if a class contains any native methods.
*/
public class ClassHasNativeVisitor implements ClassVisitor {
-
+
private boolean mHasNativeMethods = false;
-
+
public boolean hasNativeMethods() {
return mHasNativeMethods;
}
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) {
+ mHasNativeMethods = hasNativeMethods;
+ }
+
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
// pass
@@ -65,7 +73,9 @@
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
- mHasNativeMethods |= ((access & Opcodes.ACC_NATIVE) != 0);
+ if ((access & Opcodes.ACC_NATIVE) != 0) {
+ setHasNativeMethods(true, name);
+ }
return null;
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
new file mode 100644
index 0000000..d6916ae
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+
+/**
+ * Tests {@link ClassHasNativeVisitor}.
+ */
+public class ClassHasNativeVisitorTest {
+
+ @Test
+ public void testHasNative() throws IOException {
+ MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
+ ClassReader cr = new ClassReader(
+ "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithNative");
+
+ cr.accept(cv, 0 /* flags */);
+ assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
+ assertTrue(cv.hasNativeMethods());
+ }
+
+ @Test
+ public void testHasNoNative() throws IOException {
+ MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
+ ClassReader cr = new ClassReader(
+ "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithoutNative");
+
+ cr.accept(cv, 0 /* flags */);
+ assertArrayEquals(new String[0], cv.getMethodsFound());
+ assertFalse(cv.hasNativeMethods());
+ }
+
+ /**
+ * Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
+ */
+ private static class MockClassHasNativeVisitor extends ClassHasNativeVisitor {
+ private ArrayList<String> mMethodsFound = new ArrayList<String>();
+
+ public String[] getMethodsFound() {
+ return mMethodsFound.toArray(new String[mMethodsFound.size()]);
+ }
+
+ @Override
+ protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) {
+ if (hasNativeMethods) {
+ mMethodsFound.add(methodName);
+ }
+ super.setHasNativeMethods(hasNativeMethods, methodName);
+ }
+ }
+
+ /**
+ * Dummy test class with a native method.
+ */
+ public static class ClassWithNative {
+ public ClassWithNative() {
+ }
+
+ public void callTheNativeMethod() {
+ native_method();
+ }
+
+ private native void native_method();
+ }
+
+ /**
+ * Dummy test class with no native method.
+ */
+ public static class ClassWithoutNative {
+ public ClassWithoutNative() {
+ }
+
+ public void someMethod() {
+ }
+ }
+}