Merge "Add ability to run tests restricted to given annotation."
diff --git a/api/current.xml b/api/current.xml
index 7c0f4da..2729ff4 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -23748,7 +23748,7 @@
>
<parameter name="parcel" type="android.os.Parcel">
</parameter>
-<parameter name="flagz" type="int">
+<parameter name="flags" type="int">
</parameter>
</method>
<field name="CREATOR"
@@ -34626,7 +34626,7 @@
visibility="public"
>
</field>
-<field name="UIMODE_SERVICE"
+<field name="UI_MODE_SERVICE"
type="java.lang.String"
transient="false"
volatile="false"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 05bbf3b..b38aeda 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1308,14 +1308,16 @@
}
// close any cursors we are managing.
- int numCursors = mManagedCursors.size();
- for (int i = 0; i < numCursors; i++) {
- ManagedCursor c = mManagedCursors.get(i);
- if (c != null) {
- c.mCursor.close();
+ synchronized (mManagedCursors) {
+ int numCursors = mManagedCursors.size();
+ for (int i = 0; i < numCursors; i++) {
+ ManagedCursor c = mManagedCursors.get(i);
+ if (c != null) {
+ c.mCursor.close();
+ }
}
+ mManagedCursors.clear();
}
- mManagedCursors.clear();
}
/**
@@ -3778,13 +3780,15 @@
}
final void performRestart() {
- final int N = mManagedCursors.size();
- for (int i=0; i<N; i++) {
- ManagedCursor mc = mManagedCursors.get(i);
- if (mc.mReleased || mc.mUpdated) {
- mc.mCursor.requery();
- mc.mReleased = false;
- mc.mUpdated = false;
+ synchronized (mManagedCursors) {
+ final int N = mManagedCursors.size();
+ for (int i=0; i<N; i++) {
+ ManagedCursor mc = mManagedCursors.get(i);
+ if (mc.mReleased || mc.mUpdated) {
+ mc.mCursor.requery();
+ mc.mReleased = false;
+ mc.mUpdated = false;
+ }
}
}
@@ -3850,12 +3854,14 @@
" did not call through to super.onStop()");
}
- final int N = mManagedCursors.size();
- for (int i=0; i<N; i++) {
- ManagedCursor mc = mManagedCursors.get(i);
- if (!mc.mReleased) {
- mc.mCursor.deactivate();
- mc.mReleased = true;
+ synchronized (mManagedCursors) {
+ final int N = mManagedCursors.size();
+ for (int i=0; i<N; i++) {
+ ManagedCursor mc = mManagedCursors.get(i);
+ if (!mc.mReleased) {
+ mc.mCursor.deactivate();
+ mc.mReleased = true;
+ }
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 13cc3ba..0355016 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2495,7 +2495,6 @@
" did not call through to super.onPostCreate()");
}
}
- r.state = null;
}
r.paused = true;
@@ -2526,6 +2525,7 @@
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
+ Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward);
if (!r.activity.mFinished && r.startsNotResumed) {
@@ -2541,6 +2541,9 @@
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
+ // We need to keep around the original state, in case
+ // we need to be created again.
+ r.state = oldState;
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 9ba7863..cb03d2c 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -24,9 +24,8 @@
/** @hide */
interface ISearchManager {
- SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch);
+ SearchableInfo getSearchableInfo(in ComponentName launchActivity);
List<SearchableInfo> getSearchablesInGlobalSearch();
- List<SearchableInfo> getSearchablesForWebSearch();
- SearchableInfo getDefaultSearchableForWebSearch();
- void setDefaultWebSearch(in ComponentName component);
+ ComponentName getGlobalSearchActivity();
+ ComponentName getWebSearchActivity();
}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 581b436..6a02a58 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -274,7 +274,7 @@
SearchManager searchManager = (SearchManager)
mContext.getSystemService(Context.SEARCH_SERVICE);
// Try to get the searchable info for the provided component.
- mSearchable = searchManager.getSearchableInfo(componentName, false);
+ mSearchable = searchManager.getSearchableInfo(componentName);
if (mSearchable == null) {
return false;
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 99edfac..b54e53d 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -137,21 +137,11 @@
* setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); // search within your activity
* setDefaultKeyMode(DEFAULT_KEYS_SEARCH_GLOBAL); // search using platform global search</pre>
*
- * <p><b>How to enable global search with Quick Search Box.</b> In addition to searching within
+ * <p><b>How to start global search.</b> In addition to searching within
* your activity or application, you can also use the Search Manager to invoke a platform-global
- * search, which uses Quick Search Box to search across the device and the web. There are two ways
- * to do this:
- * <ul><li>You can simply define "search" within your application or activity to mean global search.
- * This is described in more detail in the
- * <a href="#SearchabilityMetadata">Searchability Metadata</a> section. Briefly, you will
- * add a single meta-data entry to your manifest, declaring that the default search
- * for your application is "*". This indicates to the system that no application-specific
- * search activity is provided, and that it should launch web-based search instead.</li>
- * <li>Simply do nothing and the default implementation of
- * {@link android.app.Activity#onSearchRequested} will cause global search to be triggered.
- * (You can also always trigger search via a direct call to {@link android.app.Activity#startSearch}.
- * This is most useful if you wish to provide local searchability <i>and</i> access to global
- * search.)</li></ul>
+ * search, which uses Quick Search Box to search across the device and the web.
+ * Override {@link android.app.Activity#onSearchRequested} and call
+ * {@link android.app.Activity#startSearch} with {@code globalSearch} set to {@code true}.
*
* <p><b>How to disable search from your activity.</b> Search is a system-wide feature and users
* will expect it to be available in all contexts. If your UI design absolutely precludes
@@ -871,12 +861,8 @@
*
* <p>The simplest way to specify this is to add a <i>search reference</i> element to the
* application entry in the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> file.
- * The value of this reference can be either of:
- * <ul><li>The name of your searchable activity.
- * It is typically prefixed by '.' to indicate that it's in the same package.</li>
- * <li>A "*" indicates that the system may select a default searchable activity, in which
- * case it will typically select web-based search.</li>
- * </ul>
+ * The value of this reference should be the name of your searchable activity.
+ * It is typically prefixed by '.' to indicate that it's in the same package.
*
* <p>Here is a snippet showing the necessary addition to the manifest entry for your
* non-searchable activities.
@@ -1662,32 +1648,15 @@
/**
* Gets the name of the global search activity.
*
- * This is currently implemented by returning the first activity that handles
- * the GLOBAL_SEARCH intent and has the GLOBAL_SEARCH permission. If we allow
- * more than one global search acitivity to be installed, this code must be changed.
- *
- * TODO: Doing this every time we start global search is inefficient. Will fix that once
- * we have settled on the right mechanism for finding the global search activity.
- *
* @hide
*/
public ComponentName getGlobalSearchActivity() {
- Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> activities =
- pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
- int count = activities.size();
- for (int i = 0; i < count; i++) {
- ActivityInfo ai = activities.get(i).activityInfo;
- if (pm.checkPermission(Manifest.permission.GLOBAL_SEARCH,
- ai.packageName) == PackageManager.PERMISSION_GRANTED) {
- return new ComponentName(ai.packageName, ai.name);
- } else {
- Log.w(TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
- + "but does not have the GLOBAL_SEARCH permission.");
- }
+ try {
+ return mService.getGlobalSearchActivity();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getGlobalSearchActivity() failed: " + ex);
+ return null;
}
- return null;
}
/**
@@ -1700,13 +1669,12 @@
* @hide
*/
public ComponentName getWebSearchActivity() {
- ComponentName globalSearch = getGlobalSearchActivity();
- if (globalSearch == null) {
+ try {
+ return mService.getWebSearchActivity();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getWebSearchActivity() failed: " + ex);
return null;
}
- Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
- intent.setPackage(globalSearch.getPackageName());
- return intent.resolveActivity(mContext.getPackageManager());
}
/**
@@ -1840,27 +1808,7 @@
*/
public SearchableInfo getSearchableInfo(ComponentName componentName) {
try {
- return mService.getSearchableInfo(componentName, false);
- } catch (RemoteException ex) {
- Log.e(TAG, "getSearchableInfo() failed: " + ex);
- return null;
- }
- }
-
- /**
- * Gets information about a searchable activity.
- *
- * @param componentName The activity to get searchable information for.
- * @param globalSearch If <code>false</code>, return information about the given activity.
- * If <code>true</code>, return information about the global search activity.
- * @return Searchable information, or <code>null</code> if the activity is not searchable.
- *
- * @hide because SearchableInfo is not part of the API.
- */
- public SearchableInfo getSearchableInfo(ComponentName componentName,
- boolean globalSearch) {
- try {
- return mService.getSearchableInfo(componentName, globalSearch);
+ return mService.getSearchableInfo(componentName);
} catch (RemoteException ex) {
Log.e(TAG, "getSearchableInfo() failed: " + ex);
return null;
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index eea9257..aca8ab4 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -12,7 +12,7 @@
*
* <p>You do not instantiate this class directly; instead, retrieve it through
* {@link android.content.Context#getSystemService
- * Context.getSystemService(Context.UIMODE_SERVICE)}.
+ * Context.getSystemService(Context.UI_MODE_SERVICE)}.
*/
public class UiModeManager {
private static final String TAG = "UiModeManager";
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 45f361e..29f388a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -167,9 +167,9 @@
/** @hide */
public static final int SYNC_OBSERVER_TYPE_ALL = 0x7fffffff;
- // Always log queries which take 100ms+; shorter queries are
+ // Always log queries which take 500ms+; shorter queries are
// sampled accordingly.
- private static final int SLOW_THRESHOLD_MILLIS = 100;
+ private static final int SLOW_THRESHOLD_MILLIS = 500;
private final Random mRandom = new Random(); // guarded by itself
public ContentResolver(Context context) {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 8fd8e28..c13dd23 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -208,11 +208,11 @@
// Things related to query logging/sampling for debugging
// slow/frequent queries during development. Always log queries
- // which take 100ms+; shorter queries are sampled accordingly.
+ // which take 500ms+; shorter queries are sampled accordingly.
// Commit statements, which are typically slow, are logged
// together with the most recently executed SQL statement, for
// disambiguation.
- private static final int QUERY_LOG_TIME_IN_MILLIS = 100;
+ private static final int QUERY_LOG_TIME_IN_MILLIS = 500;
private static final int QUERY_LOG_SQL_LENGTH = 64;
private static final String COMMIT_SQL = "COMMIT;";
private final Random mRandom = new Random();
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index fbc4a81..2eebe77 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -123,24 +123,15 @@
* Returns the SearchableInfo for a given activity.
*
* @param launchActivity The activity from which we're launching this search.
- * @param globalSearch If false, this will only launch the search that has been specifically
- * defined by the application (which is usually defined as a local search). If no default
- * search is defined in the current application or activity, no search will be launched.
- * If true, this will always launch a platform-global (e.g. web-based) search instead.
* @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available.
*/
- public SearchableInfo getSearchableInfo(final ComponentName launchActivity,
- final boolean globalSearch) {
- if (globalSearch) {
- return getSearchables().getDefaultSearchable();
- } else {
- if (launchActivity == null) {
- Log.e(TAG, "getSearchableInfo(), activity == null");
- return null;
- }
- return getSearchables().getSearchableInfo(launchActivity);
+ public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
+ if (launchActivity == null) {
+ Log.e(TAG, "getSearchableInfo(), activity == null");
+ return null;
}
+ return getSearchables().getSearchableInfo(launchActivity);
}
/**
@@ -151,27 +142,17 @@
}
/**
- * Returns a list of the searchable activities that handle web searches.
- * Can be called from any thread.
+ * Gets the name of the global search activity.
*/
- public List<SearchableInfo> getSearchablesForWebSearch() {
- return getSearchables().getSearchablesForWebSearchList();
+ public ComponentName getGlobalSearchActivity() {
+ return getSearchables().getGlobalSearchActivity();
}
/**
- * Returns the default searchable activity for web searches.
- * Can be called from any thread.
+ * Gets the name of the web search activity.
*/
- public SearchableInfo getDefaultSearchableForWebSearch() {
- return getSearchables().getDefaultSearchableForWebSearch();
+ public ComponentName getWebSearchActivity() {
+ return getSearchables().getWebSearchActivity();
}
- /**
- * Sets the default searchable activity for web searches.
- * Can be called from any thread.
- */
- public void setDefaultWebSearch(final ComponentName component) {
- getSearchables().setDefaultWebSearch(component);
- broadcastSearchablesChanged();
- }
}
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
index cbb63a5..279c17d 100644
--- a/core/java/android/server/search/Searchables.java
+++ b/core/java/android/server/search/Searchables.java
@@ -16,14 +16,12 @@
package android.server.search;
-import com.android.internal.app.ResolverActivity;
-
+import android.Manifest;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -52,9 +50,8 @@
private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
private ArrayList<SearchableInfo> mSearchablesList = null;
private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
- private ArrayList<SearchableInfo> mSearchablesForWebSearchList = null;
- private SearchableInfo mDefaultSearchable = null;
- private SearchableInfo mDefaultSearchableForWebSearch = null;
+ private ComponentName mGlobalSearchActivity = null;
+ private ComponentName mWebSearchActivity = null;
public static String GOOGLE_SEARCH_COMPONENT_NAME =
"com.android.googlesearch/.GoogleSearch";
@@ -131,10 +128,9 @@
// Irrespective of source, if a reference was found, follow it.
if (refActivityName != null)
{
- // An app or activity can declare that we should simply launch
- // "system default search" if search is invoked.
+ // This value is deprecated, return null
if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
- return getDefaultSearchable();
+ return null;
}
String pkg = activity.getPackageName();
ComponentName referredActivity;
@@ -164,20 +160,6 @@
}
/**
- * Provides the system-default search activity, which you can use
- * whenever getSearchableInfo() returns null;
- *
- * @return Returns the system-default search activity, null if never defined
- */
- public synchronized SearchableInfo getDefaultSearchable() {
- return mDefaultSearchable;
- }
-
- public synchronized boolean isDefaultSearchable(SearchableInfo searchable) {
- return searchable == mDefaultSearchable;
- }
-
- /**
* Builds an entire list (suitable for display) of
* activities that are searchable, by iterating the entire set of
* ACTION_SEARCH & ACTION_WEB_SEARCH intents.
@@ -205,8 +187,6 @@
= new ArrayList<SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
= new ArrayList<SearchableInfo>();
- ArrayList<SearchableInfo> newSearchablesForWebSearchList
- = new ArrayList<SearchableInfo>();
final PackageManager pm = mContext.getPackageManager();
@@ -244,127 +224,71 @@
}
}
- if (webSearchInfoList != null) {
- for (int i = 0; i < webSearchInfoList.size(); ++i) {
- ActivityInfo ai = webSearchInfoList.get(i).activityInfo;
- ComponentName component = new ComponentName(ai.packageName, ai.name);
- SearchableInfo searchable = newSearchablesMap.get(component);
- if (searchable == null) {
- Log.w(LOG_TAG, "did not find component in searchables: " + component);
- } else {
- newSearchablesForWebSearchList.add(searchable);
- }
- }
- }
+ // Find the global search activity
+ ComponentName newGlobalSearchActivity = findGlobalSearchActivity();
- // Find the global search provider
- Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
- ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm);
- SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity);
-
- if (newDefaultSearchable == null) {
- Log.w(LOG_TAG, "No searchable info found for new default searchable activity "
- + globalSearchActivity);
- }
-
- // Find the default web search provider.
- ComponentName webSearchActivity = getPreferredWebSearchActivity(mContext);
- SearchableInfo newDefaultSearchableForWebSearch = null;
- if (webSearchActivity != null) {
- newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity);
- }
- if (newDefaultSearchableForWebSearch == null) {
- Log.w(LOG_TAG, "No searchable info found for new default web search activity "
- + webSearchActivity);
- }
+ // Find the web search activity
+ ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);
// Store a consistent set of new values
synchronized (this) {
mSearchablesMap = newSearchablesMap;
mSearchablesList = newSearchablesList;
mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
- mSearchablesForWebSearchList = newSearchablesForWebSearchList;
- mDefaultSearchable = newDefaultSearchable;
- mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch;
+ mGlobalSearchActivity = newGlobalSearchActivity;
+ mWebSearchActivity = newWebSearchActivity;
}
}
/**
- * Checks if the given activity component is present in the system and if so makes it the
- * preferred activity for handling ACTION_WEB_SEARCH.
- * @param component Name of the component to check and set as preferred.
- * @param action Intent action for which this activity is to be set as preferred.
- * @return true if component was detected and set as preferred activity, false if not.
+ * Finds the global search activity.
+ *
+ * This is currently implemented by returning the first activity that handles
+ * the GLOBAL_SEARCH intent and has the GLOBAL_SEARCH permission. If we allow
+ * more than one global search activity to be installed, this code must be changed.
*/
- private static boolean setPreferredActivity(Context context,
- ComponentName component, String action) {
- Log.d(LOG_TAG, "Checking component " + component);
- PackageManager pm = context.getPackageManager();
- ActivityInfo ai;
- try {
- ai = pm.getActivityInfo(component, 0);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
+ private ComponentName findGlobalSearchActivity() {
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> activities =
+ pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ int count = activities == null ? 0 : activities.size();
+ for (int i = 0; i < count; i++) {
+ ActivityInfo ai = activities.get(i).activityInfo;
+ if (pm.checkPermission(Manifest.permission.GLOBAL_SEARCH,
+ ai.packageName) == PackageManager.PERMISSION_GRANTED) {
+ return new ComponentName(ai.packageName, ai.name);
+ } else {
+ Log.w(LOG_TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
+ + "but does not have the GLOBAL_SEARCH permission.");
+ }
}
-
- // The code here to find the value for bestMatch is heavily inspired by the code
- // in ResolverActivity where the preferred activity is set.
- Intent intent = new Intent(action);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- List<ResolveInfo> webSearchActivities = pm.queryIntentActivities(intent, 0);
- ComponentName set[] = new ComponentName[webSearchActivities.size()];
- int bestMatch = 0;
- for (int i = 0; i < webSearchActivities.size(); ++i) {
- ResolveInfo ri = webSearchActivities.get(i);
- set[i] = new ComponentName(ri.activityInfo.packageName,
- ri.activityInfo.name);
- if (ri.match > bestMatch) bestMatch = ri.match;
- }
-
- Log.d(LOG_TAG, "Setting preferred web search activity to " + component);
- IntentFilter filter = new IntentFilter(action);
- filter.addCategory(Intent.CATEGORY_DEFAULT);
- pm.replacePreferredActivity(filter, bestMatch, set, component);
- return true;
+ Log.w(LOG_TAG, "No global search activity found");
+ return null;
}
- private static ComponentName getPreferredWebSearchActivity(Context context) {
- // Check if we have a preferred web search activity.
- Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
- PackageManager pm = context.getPackageManager();
- ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
-
- if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) {
- Log.d(LOG_TAG, "No preferred activity set for action web search.");
-
- // The components in the providers array are checked in the order of declaration so the
- // first one has the highest priority. If the component exists in the system it is set
- // as the preferred activity to handle intent action web search.
- String[] preferredActivities = context.getResources().getStringArray(
- com.android.internal.R.array.default_web_search_providers);
- for (String componentName : preferredActivities) {
- ComponentName component = ComponentName.unflattenFromString(componentName);
- if (setPreferredActivity(context, component, Intent.ACTION_WEB_SEARCH)) {
- return component;
- }
- }
- } else {
- // If the current preferred activity is GoogleSearch, and we detect
- // EnhancedGoogleSearch installed as well, set the latter as preferred since that
- // is a superset and provides more functionality.
- ComponentName cn = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
- if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) {
- ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString(
- ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME);
- if (setPreferredActivity(context, enhancedGoogleSearch,
- Intent.ACTION_WEB_SEARCH)) {
- return enhancedGoogleSearch;
- }
- }
+ /**
+ * Finds the web search activity.
+ *
+ * Only looks in the package of the global search activity.
+ */
+ private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) {
+ if (globalSearchActivity == null) {
+ return null;
}
-
- if (ri == null) return null;
- return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
+ Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
+ intent.setPackage(globalSearchActivity.getPackageName());
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> activities =
+ pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ int count = activities == null ? 0 : activities.size();
+ for (int i = 0; i < count; i++) {
+ ActivityInfo ai = activities.get(i).activityInfo;
+ // TODO: do some sanity checks here?
+ return new ComponentName(ai.packageName, ai.name);
+ }
+ Log.w(LOG_TAG, "No web search activity found");
+ return null;
}
/**
@@ -383,24 +307,16 @@
}
/**
- * Returns a list of the searchable activities that handle web searches.
+ * Gets the name of the global search activity.
*/
- public synchronized ArrayList<SearchableInfo> getSearchablesForWebSearchList() {
- return new ArrayList<SearchableInfo>(mSearchablesForWebSearchList);
+ public synchronized ComponentName getGlobalSearchActivity() {
+ return mGlobalSearchActivity;
}
/**
- * Returns the default searchable activity for web searches.
+ * Gets the name of the web search activity.
*/
- public synchronized SearchableInfo getDefaultSearchableForWebSearch() {
- return mDefaultSearchableForWebSearch;
- }
-
- /**
- * Sets the default searchable activity for web searches.
- */
- public synchronized void setDefaultWebSearch(ComponentName component) {
- setPreferredActivity(mContext, component, Intent.ACTION_WEB_SEARCH);
- buildSearchableList();
+ public synchronized ComponentName getWebSearchActivity() {
+ return mWebSearchActivity;
}
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index a79bbee..17d5bb7 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1960,7 +1960,6 @@
if (getHeight() > 0 && getChildCount() > 0) {
// We do not lose focus initiating a touch (since AbsListView is focusable in
// touch mode). Force an initial layout to get rid of the selection.
- mLayoutMode = LAYOUT_NORMAL;
layoutChildren();
}
} else {
@@ -3118,7 +3117,9 @@
void hideSelector() {
if (mSelectedPosition != INVALID_POSITION) {
- mResurrectToPosition = mSelectedPosition;
+ if (mLayoutMode != LAYOUT_SPECIFIC) {
+ mResurrectToPosition = mSelectedPosition;
+ }
if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
mResurrectToPosition = mNextSelectedPosition;
}
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index c939e3f..2b3b98d 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -28,8 +28,6 @@
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Interpolator;
-
/**
* An abstract base class for spinner widgets. SDK users will probably not
@@ -38,24 +36,21 @@
* @attr ref android.R.styleable#AbsSpinner_entries
*/
public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
-
SpinnerAdapter mAdapter;
int mHeightMeasureSpec;
int mWidthMeasureSpec;
boolean mBlockLayoutRequests;
+
int mSelectionLeftPadding = 0;
int mSelectionTopPadding = 0;
int mSelectionRightPadding = 0;
int mSelectionBottomPadding = 0;
- Rect mSpinnerPadding = new Rect();
- View mSelectedView = null;
- Interpolator mInterpolator;
+ final Rect mSpinnerPadding = new Rect();
- RecycleBin mRecycler = new RecycleBin();
+ final RecycleBin mRecycler = new RecycleBin();
private DataSetObserver mDataSetObserver;
-
/** Temporary frame to hold a child View's frame rectangle */
private Rect mTouchFrame;
@@ -95,7 +90,6 @@
setWillNotDraw(false);
}
-
/**
* The Adapter is used to provide the data which backs this Spinner.
* It also provides methods to transform spinner items based on their position
@@ -190,7 +184,7 @@
boolean needsMeasuring = true;
int selectedPosition = getSelectedItemPosition();
- if (selectedPosition >= 0 && mAdapter != null) {
+ if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
// Try looking in the recycler. (Maybe we were measured once already)
View view = mRecycler.get(selectedPosition);
if (view == null) {
@@ -237,7 +231,6 @@
mWidthMeasureSpec = widthMeasureSpec;
}
-
int getChildHeight(View child) {
return child.getMeasuredHeight();
}
@@ -254,26 +247,17 @@
}
void recycleAllViews() {
- int childCount = getChildCount();
+ final int childCount = getChildCount();
final AbsSpinner.RecycleBin recycleBin = mRecycler;
+ final int position = mFirstPosition;
// All views go in recycler
- for (int i=0; i<childCount; i++) {
+ for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
- int index = mFirstPosition + i;
+ int index = position + i;
recycleBin.put(index, v);
}
}
-
- @Override
- void handleDataChanged() {
- // FIXME -- this is called from both measure and layout.
- // This is harmless right now, but we don't want to do redundant work if
- // this gets more complicated
- super.handleDataChanged();
- }
-
-
/**
* Jump directly to a specific item in the adapter data.
@@ -284,7 +268,6 @@
position <= mFirstPosition + getChildCount() - 1;
setSelectionInt(position, shouldAnimate);
}
-
@Override
public void setSelection(int position) {
@@ -335,8 +318,6 @@
}
}
-
-
@Override
public SpinnerAdapter getAdapter() {
return mAdapter;
@@ -452,7 +433,7 @@
}
class RecycleBin {
- private SparseArray<View> mScrapHeap = new SparseArray<View>();
+ private final SparseArray<View> mScrapHeap = new SparseArray<View>();
public void put(int position, View v) {
mScrapHeap.put(position, v);
@@ -469,12 +450,7 @@
}
return result;
}
-
- View peek(int position) {
- // System.out.print("Looking for " + position);
- return mScrapHeap.get(position);
- }
-
+
void clear() {
final SparseArray<View> scrapHeap = mScrapHeap;
final int count = scrapHeap.size();
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index f56b15c..107b145 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
+import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -48,7 +49,6 @@
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
-import android.util.AttributeSet;
import com.android.internal.R;
@@ -322,24 +322,24 @@
public Button getButton(int whichButton) {
switch (whichButton) {
case DialogInterface.BUTTON_POSITIVE:
- return mButtonPositiveMessage != null ? mButtonPositive : null;
+ return mButtonPositive;
case DialogInterface.BUTTON_NEGATIVE:
- return mButtonNegativeMessage != null ? mButtonNegative : null;
+ return mButtonNegative;
case DialogInterface.BUTTON_NEUTRAL:
- return mButtonNeutralMessage != null ? mButtonNeutral : null;
+ return mButtonNeutral;
default:
return null;
}
}
+ @SuppressWarnings({"UnusedDeclaration"})
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mScrollView != null && mScrollView.executeKeyEvent(event)) return true;
- return false;
+ return mScrollView != null && mScrollView.executeKeyEvent(event);
}
+ @SuppressWarnings({"UnusedDeclaration"})
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (mScrollView != null && mScrollView.executeKeyEvent(event)) return true;
- return false;
+ return mScrollView != null && mScrollView.executeKeyEvent(event);
}
private void setupView() {
@@ -469,7 +469,6 @@
}
private boolean setupButtons() {
- View defaultButton = null;
int BIT_BUTTON_POSITIVE = 1;
int BIT_BUTTON_NEGATIVE = 2;
int BIT_BUTTON_NEUTRAL = 4;
@@ -482,7 +481,6 @@
} else {
mButtonPositive.setText(mButtonPositiveText);
mButtonPositive.setVisibility(View.VISIBLE);
- defaultButton = mButtonPositive;
whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
}
@@ -495,9 +493,6 @@
mButtonNegative.setText(mButtonNegativeText);
mButtonNegative.setVisibility(View.VISIBLE);
- if (defaultButton == null) {
- defaultButton = mButtonNegative;
- }
whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
}
@@ -510,9 +505,6 @@
mButtonNeutral.setText(mButtonNeutralText);
mButtonNeutral.setVisibility(View.VISIBLE);
- if (defaultButton == null) {
- defaultButton = mButtonNeutral;
- }
whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
}
@@ -565,8 +557,6 @@
R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
int bottomMedium = a.getResourceId(
R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
- int centerMedium = a.getResourceId(
- R.styleable.AlertDialog_centerMedium, R.drawable.popup_center_medium);
/*
* We now set the background of all of the sections of the alert.
@@ -596,7 +586,7 @@
*/
views[pos] = (contentPanel.getVisibility() == View.GONE)
? null : contentPanel;
- light[pos] = mListView == null ? false : true;
+ light[pos] = mListView != null;
pos++;
if (customPanel != null) {
views[pos] = customPanel;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index dfcc8f7..71ccb3b 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -59,6 +59,13 @@
// Current on-disk Parcel version
private static final int VERSION = 42;
+ // The maximum number of names wakelocks we will keep track of
+ // per uid; once the limit is reached, we batch the remaining wakelocks
+ // in to one common name.
+ private static final int MAX_WAKELOCKS_PER_UID = 20;
+
+ private static final String BATCHED_WAKELOCK_NAME = "*overflow*";
+
private static int sNumSpeedSteps;
private final File mFile;
@@ -1757,7 +1764,12 @@
String wakelockName = in.readString();
Uid.Wakelock wakelock = new Wakelock();
wakelock.readFromParcelLocked(unpluggables, in);
- mWakelockStats.put(wakelockName, wakelock);
+ if (mWakelockStats.size() < MAX_WAKELOCKS_PER_UID) {
+ // We will just drop some random set of wakelocks if
+ // the previous run of the system was an older version
+ // that didn't impose a limit.
+ mWakelockStats.put(wakelockName, wakelock);
+ }
}
int numSensors = in.readInt();
@@ -2583,8 +2595,14 @@
public StopwatchTimer getWakeTimerLocked(String name, int type) {
Wakelock wl = mWakelockStats.get(name);
if (wl == null) {
- wl = new Wakelock();
- mWakelockStats.put(name, wl);
+ if (mWakelockStats.size() > MAX_WAKELOCKS_PER_UID) {
+ name = BATCHED_WAKELOCK_NAME;
+ wl = mWakelockStats.get(name);
+ }
+ if (wl == null) {
+ wl = new Wakelock();
+ mWakelockStats.put(name, wl);
+ }
}
StopwatchTimer t = null;
switch (type) {
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png
index 7acd0df..72faccf 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png
index a8b843a..369be10 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png
index cfbc092..7e996ec 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png
index 3d0d16e..44668b3 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png
index 2ccd3da..3a4571e 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png
index 966ea44..60dc632 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png b/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png
index febf222..437fbc7 100644
--- a/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png
+++ b/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png b/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png
index 70a200b..a679426 100644
--- a/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png
+++ b/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png b/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png
index 6f2989f..eb95f22 100644
--- a/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png
+++ b/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png
Binary files differ
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 7ae68f9..25a41f8 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -80,7 +80,8 @@
android:paddingTop="2dip"
android:paddingBottom="12dip"
android:paddingLeft="14dip"
- android:paddingRight="10dip">
+ android:paddingRight="10dip"
+ android:overscrollMode="ifContentScrolls">
<TextView android:id="@+id/message"
style="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/search_bar.xml b/core/res/res/layout/search_bar.xml
index cf246ba..7935e2a 100644
--- a/core/res/res/layout/search_bar.xml
+++ b/core/res/res/layout/search_bar.xml
@@ -34,7 +34,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="12dip"
- android:paddingRight="12dip"
+ android:paddingRight="6dip"
android:paddingTop="7dip"
android:paddingBottom="16dip"
android:background="@drawable/search_plate_global" >
@@ -95,7 +95,10 @@
android:id="@+id/search_voice_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginLeft="8dip"
+ android:layout_marginLeft="0dip"
+ android:layout_marginTop="-6.5dip"
+ android:layout_marginBottom="-7dip"
+ android:layout_marginRight="-5dip"
android:background="@drawable/btn_search_dialog_voice"
android:src="@android:drawable/ic_btn_speak_now"
/>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index cdc15c2..0c065ef 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -127,15 +127,4 @@
<item><xliff:g id="id">ime</xliff:g></item>
</string-array>
- <!-- Do not translate. Each string points to the component name of an ACTION_WEB_SEARCH
- handling activity. On startup if there were no preferred ACTION_WEB_SEARCH handlers,
- the first component from this list which is found to be installed is set as the
- preferred activity. -->
- <string-array name="default_web_search_providers">
- <item>com.google.android.googlequicksearchbox/.google.GoogleSearch</item>
- <item>com.android.quicksearchbox/.google.GoogleSearch</item>
- <item>com.google.android.providers.enhancedgooglesearch/.Launcher</item>
- <item>com.android.googlesearch/.GoogleSearch</item>
- <item>com.android.websearch/.Search.1</item>
- </string-array>
</resources>
diff --git a/core/tests/coretests/src/android/app/SearchablesTest.java b/core/tests/coretests/src/android/app/SearchablesTest.java
index 9b4520e..6cb31d4 100644
--- a/core/tests/coretests/src/android/app/SearchablesTest.java
+++ b/core/tests/coretests/src/android/app/SearchablesTest.java
@@ -64,39 +64,7 @@
* findActionKey works
* getIcon works
*/
-
- /**
- * The goal of this test is to confirm proper operation of the
- * SearchableInfo helper class.
- *
- * TODO: The metadata source needs to be mocked out because adding
- * searchability metadata via this test is causing it to leak into the
- * real system. So for now I'm just going to test for existence of the
- * GlobalSearch app (which is searchable).
- */
- public void testSearchableGlobalSearch() {
- // test basic array & hashmap
- Searchables searchables = new Searchables(mContext);
- searchables.buildSearchableList();
- // test linkage from another activity
- // TODO inject this via mocking into the package manager.
- // TODO for now, just check for searchable GlobalSearch app (this isn't really a unit test)
- ComponentName thisActivity = new ComponentName(
- "com.android.globalsearch",
- "com.android.globalsearch.GlobalSearch");
-
- SearchableInfo si = searchables.getSearchableInfo(thisActivity);
- assertNotNull(si);
- assertEquals(thisActivity, si.getSearchActivity());
-
- Context appContext = si.getActivityContext(mContext);
- assertNotNull(appContext);
- MoreAsserts.assertNotEqual(appContext, mContext);
- assertEquals("Quick Search Box", appContext.getString(si.getHintId()));
- assertEquals("Quick Search Box", appContext.getString(si.getLabelId()));
- }
-
/**
* Test that non-searchable activities return no searchable info (this would typically
* trigger the use of the default searchable e.g. contacts)
@@ -113,18 +81,7 @@
SearchableInfo si = searchables.getSearchableInfo(nonActivity);
assertNull(si);
}
-
- /**
- * Test that there is a default searchable (aka global search provider).
- */
- public void testDefaultSearchable() {
- Searchables searchables = new Searchables(mContext);
- searchables.buildSearchableList();
- SearchableInfo si = searchables.getDefaultSearchable();
- checkSearchable(si);
- assertTrue(searchables.isDefaultSearchable(si));
- }
-
+
/**
* This is an attempt to run the searchable info list with a mocked context. Here are some
* things I'd like to test.
@@ -371,7 +328,8 @@
public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
assertNotNull(intent);
assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH)
- || intent.getAction().equals(Intent.ACTION_WEB_SEARCH));
+ || intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
+ || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
switch (mSearchablesMode) {
case SEARCHABLES_PASSTHROUGH:
return mRealPackageManager.queryIntentActivities(intent, flags);
@@ -472,6 +430,20 @@
throw new UnsupportedOperationException();
}
}
+
+ @Override
+ public int checkPermission(String permName, String pkgName) {
+ assertNotNull(permName);
+ assertNotNull(pkgName);
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.checkPermission(permName, pkgName);
+ case SEARCHABLES_MOCK_ZERO:
+ return PackageManager.PERMISSION_DENIED;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
}
}
diff --git a/include/binder/Parcel.h b/include/binder/Parcel.h
index ba6c711..66c34b2 100644
--- a/include/binder/Parcel.h
+++ b/include/binder/Parcel.h
@@ -30,6 +30,7 @@
class ProcessState;
class String8;
class TextOutput;
+class Flattenable;
struct flat_binder_object; // defined in support_p/binder_module.h
@@ -81,6 +82,7 @@
status_t writeString16(const char16_t* str, size_t len);
status_t writeStrongBinder(const sp<IBinder>& val);
status_t writeWeakBinder(const wp<IBinder>& val);
+ status_t write(const Flattenable& val);
// Place a native_handle into the parcel (the native_handle's file-
// descriptors are dup'ed, so it is safe to delete the native_handle
@@ -119,7 +121,7 @@
const char16_t* readString16Inplace(size_t* outLen) const;
sp<IBinder> readStrongBinder() const;
wp<IBinder> readWeakBinder() const;
-
+ status_t read(Flattenable& val) const;
// Retrieve native_handle from the parcel. This returns a copy of the
// parcel's native_handle (the caller takes ownership). The caller
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index f8bc7ab..974075d 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -96,6 +96,7 @@
kRequiresFlushBeforeShutdown = 64,
kDefersOutputBufferAllocation = 128,
kDecoderLiesAboutNumberOfChannels = 256,
+ kInputBufferSizesAreBogus = 512,
};
struct BufferInfo {
diff --git a/include/ui/GraphicBuffer.h b/include/ui/GraphicBuffer.h
index b9c491be..e72b6b3 100644
--- a/include/ui/GraphicBuffer.h
+++ b/include/ui/GraphicBuffer.h
@@ -23,6 +23,7 @@
#include <ui/android_native_buffer.h>
#include <ui/PixelFormat.h>
#include <ui/Rect.h>
+#include <utils/Flattenable.h>
#include <pixelflinger/pixelflinger.h>
struct android_native_buffer_t;
@@ -30,7 +31,6 @@
namespace android {
class GraphicBufferMapper;
-class Parcel;
// ===========================================================================
// GraphicBuffer
@@ -40,7 +40,7 @@
: public EGLNativeBase<
android_native_buffer_t,
GraphicBuffer,
- LightRefBase<GraphicBuffer> >
+ LightRefBase<GraphicBuffer> >, public Flattenable
{
public:
@@ -97,7 +97,6 @@
uint32_t getVerticalStride() const;
protected:
- GraphicBuffer(const Parcel& reply);
virtual ~GraphicBuffer();
enum {
@@ -122,8 +121,16 @@
status_t initSize(uint32_t w, uint32_t h, PixelFormat format,
uint32_t usage);
- static status_t writeToParcel(Parcel* reply,
- android_native_buffer_t const* buffer);
+ void free_handle();
+
+ // Flattenable interface
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void* buffer, size_t size,
+ int fds[], size_t count) const;
+ status_t unflatten(void const* buffer, size_t size,
+ int fds[], size_t count);
+
GraphicBufferMapper& mBufferMapper;
ssize_t mInitCheck;
diff --git a/include/utils/Flattenable.h b/include/utils/Flattenable.h
new file mode 100644
index 0000000..852be3b
--- /dev/null
+++ b/include/utils/Flattenable.h
@@ -0,0 +1,62 @@
+/*
+ * 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_UTILS_FLATTENABLE_H
+#define ANDROID_UTILS_FLATTENABLE_H
+
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+class Flattenable
+{
+public:
+ // size in bytes of the flattened object
+ virtual size_t getFlattenedSize() const = 0;
+
+ // number of file descriptors to flatten
+ virtual size_t getFdCount() const = 0;
+
+ // flattens the object into buffer.
+ // size should be at least of getFlattenedSize()
+ // file descriptors are written in the fds[] array but ownership is
+ // not transfered (ie: they must be dupped by the caller of
+ // flatten() if needed).
+ virtual status_t flatten(void* buffer, size_t size,
+ int fds[], size_t count) const = 0;
+
+ // unflattens the object from buffer.
+ // size should be equal to the value of getFlattenedSize() when the
+ // object was flattened.
+ // unflattened file descriptors are found in the fds[] array and
+ // don't need to be dupped(). ie: the caller of unflatten doesn't
+ // keep ownership. If a fd is not retained by unflatten() it must be
+ // explicitly closed.
+ virtual status_t unflatten(void const* buffer, size_t size,
+ int fds[], size_t count) = 0;
+
+protected:
+ virtual ~Flattenable() = 0;
+
+};
+
+}; // namespace android
+
+
+#endif /* ANDROID_UTILS_FLATTENABLE_H */
diff --git a/keystore/java/android/security/SystemKeyStore.java b/keystore/java/android/security/SystemKeyStore.java
index 61a4293..abdb0ae 100644
--- a/keystore/java/android/security/SystemKeyStore.java
+++ b/keystore/java/android/security/SystemKeyStore.java
@@ -17,6 +17,7 @@
package android.security;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Process;
import java.io.File;
@@ -92,6 +93,8 @@
fos.write(retKey);
fos.flush();
fos.close();
+ FileUtils.setPermissions(keyFile.getName(), (FileUtils.S_IRUSR | FileUtils.S_IWUSR),
+ -1, -1);
} catch (IOException ioe) {
return null;
}
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index e397bce..00d2210 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -28,6 +28,7 @@
#include <utils/String16.h>
#include <utils/TextOutput.h>
#include <utils/misc.h>
+#include <utils/Flattenable.h>
#include <private/binder/binder_module.h>
@@ -675,6 +676,42 @@
return writeObject(obj, true);
}
+status_t Parcel::write(const Flattenable& val)
+{
+ status_t err;
+
+ // size if needed
+ size_t len = val.getFlattenedSize();
+ size_t fd_count = val.getFdCount();
+
+ err = this->writeInt32(len);
+ if (err) return err;
+
+ err = this->writeInt32(fd_count);
+ if (err) return err;
+
+ // payload
+ void* buf = this->writeInplace(PAD_SIZE(len));
+ if (buf == NULL)
+ return BAD_VALUE;
+
+ int* fds = NULL;
+ if (fd_count) {
+ fds = new int[fd_count];
+ }
+
+ err = val.flatten(buf, len, fds, fd_count);
+ for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
+ err = this->writeDupFileDescriptor( fds[i] );
+ }
+
+ if (fd_count) {
+ delete [] fds;
+ }
+
+ return err;
+}
+
status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
{
const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity;
@@ -713,7 +750,6 @@
goto restart_write;
}
-
void Parcel::remove(size_t start, size_t amt)
{
LOG_ALWAYS_FATAL("Parcel::remove() not yet implemented!");
@@ -940,6 +976,38 @@
return BAD_TYPE;
}
+status_t Parcel::read(Flattenable& val) const
+{
+ // size
+ const size_t len = this->readInt32();
+ const size_t fd_count = this->readInt32();
+
+ // payload
+ void const* buf = this->readInplace(PAD_SIZE(len));
+ if (buf == NULL)
+ return BAD_VALUE;
+
+ int* fds = NULL;
+ if (fd_count) {
+ fds = new int[fd_count];
+ }
+
+ status_t err = NO_ERROR;
+ for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
+ fds[i] = dup(this->readFileDescriptor());
+ if (fds[i] < 0) err = BAD_VALUE;
+ }
+
+ if (err == NO_ERROR) {
+ err = val.unflatten(buf, len, fds, fd_count);
+ }
+
+ if (fd_count) {
+ delete [] fds;
+ }
+
+ return err;
+}
const flat_binder_object* Parcel::readObject(bool nullMetaData) const
{
const size_t DPOS = mDataPos;
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index dec993a..cc3a74fb 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -546,6 +546,8 @@
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mGL.mMaxFragmentTextureImageUnits);
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &mGL.mMaxFragmentUniformVectors);
+
+ mGL.OES_texture_npot = NULL != strstr((const char *)mGL.mExtensions, "GL_OES_texture_npot");
}
}
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index 03e65f1..04bd748 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -163,6 +163,8 @@
mutable const ObjectBase * mObjHead;
+ bool ext_OES_texture_npot() const {return mGL.OES_texture_npot;}
+
protected:
Device *mDev;
@@ -196,6 +198,8 @@
int32_t mMaxVertexAttribs;
int32_t mMaxVertexUniformVectors;
int32_t mMaxVertexTextureUnits;
+
+ bool OES_texture_npot;
} mGL;
uint32_t mWidth;
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 15f3269..c17b94c 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -109,7 +109,7 @@
}
if (mSamplers[ct].get()) {
- mSamplers[ct]->setupGL(rsc);
+ mSamplers[ct]->setupGL(rsc, mTextures[ct]->getType()->getIsNp2());
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -159,7 +159,7 @@
glBindTexture(GL_TEXTURE_2D, mTextures[ct]->getTextureID());
rsc->checkError("ProgramFragment::setupGL2 tex bind");
if (mSamplers[ct].get()) {
- mSamplers[ct]->setupGL(rsc);
+ mSamplers[ct]->setupGL(rsc, mTextures[ct]->getType()->getIsNp2());
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index 7552d54..71f508f 100644
--- a/libs/rs/rsSampler.cpp
+++ b/libs/rs/rsSampler.cpp
@@ -53,7 +53,7 @@
{
}
-void Sampler::setupGL(const Context *rsc)
+void Sampler::setupGL(const Context *rsc, bool npot)
{
GLenum trans[] = {
GL_NEAREST, //RS_SAMPLER_NEAREST,
@@ -64,11 +64,21 @@
};
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, trans[mMinFilter]);
+ bool forceNonMip = false;
+ if (!rsc->ext_OES_texture_npot() && npot) {
+ forceNonMip = true;
+ }
+
+ if ((mMinFilter == RS_SAMPLER_LINEAR_MIP_LINEAR) && forceNonMip) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ } else {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, trans[mMinFilter]);
+ }
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, trans[mMagFilter]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, trans[mWrapS]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, trans[mWrapT]);
+
rsc->checkError("ProgramFragment::setupGL2 tex env");
}
diff --git a/libs/rs/rsSampler.h b/libs/rs/rsSampler.h
index 9e20a2f..0506081 100644
--- a/libs/rs/rsSampler.h
+++ b/libs/rs/rsSampler.h
@@ -41,7 +41,7 @@
virtual ~Sampler();
void bind(Allocation *);
- void setupGL(const Context *);
+ void setupGL(const Context *, bool npot);
void bindToContext(SamplerState *, uint32_t slot);
void unbindFromContext(SamplerState *);
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index 9d24c6c..c09e979 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -283,6 +283,24 @@
mElement->dumpLOGV(buf);
}
+bool Type::getIsNp2() const
+{
+ uint32_t x = getDimX();
+ uint32_t y = getDimY();
+ uint32_t z = getDimZ();
+
+ if (x && (x & (x-1))) {
+ return true;
+ }
+ if (y && (y & (y-1))) {
+ return true;
+ }
+ if (z && (z & (z-1))) {
+ return true;
+ }
+ return false;
+}
+
//////////////////////////////////////////////////
//
diff --git a/libs/rs/rsType.h b/libs/rs/rsType.h
index 28e6274..c25577c 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -56,6 +56,7 @@
uint32_t getLODOffset(uint32_t lod, uint32_t x, uint32_t y, uint32_t z) const;
uint32_t getLODCount() const {return mLODCount;}
+ bool getIsNp2() const;
void setElement(const Element *e) {mElement.set(e);}
@@ -65,6 +66,7 @@
void setDimFaces(bool v) {mFaces = v;}
void setDimLOD(bool v) {mDimLOD = v;}
+
void clear();
void compute();
diff --git a/libs/surfaceflinger_client/ISurface.cpp b/libs/surfaceflinger_client/ISurface.cpp
index 9125146..bb86199 100644
--- a/libs/surfaceflinger_client/ISurface.cpp
+++ b/libs/surfaceflinger_client/ISurface.cpp
@@ -78,7 +78,8 @@
data.writeInt32(bufferIdx);
data.writeInt32(usage);
remote()->transact(REQUEST_BUFFER, data, &reply);
- sp<GraphicBuffer> buffer = new GraphicBuffer(reply);
+ sp<GraphicBuffer> buffer = new GraphicBuffer();
+ reply.read(*buffer);
return buffer;
}
@@ -141,7 +142,9 @@
int bufferIdx = data.readInt32();
int usage = data.readInt32();
sp<GraphicBuffer> buffer(requestBuffer(bufferIdx, usage));
- return GraphicBuffer::writeToParcel(reply, buffer.get());
+ if (buffer == NULL)
+ return BAD_VALUE;
+ return reply->write(*buffer);
}
case REGISTER_BUFFERS: {
CHECK_INTERFACE(ISurface, data, reply);
diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index 6a5c8a9..ba1fd9c 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -14,12 +14,12 @@
* limitations under the License.
*/
+#define LOG_TAG "GraphicBuffer"
+
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
-#include <binder/Parcel.h>
-
#include <utils/Errors.h>
#include <utils/Log.h>
@@ -77,34 +77,21 @@
handle = inHandle;
}
-GraphicBuffer::GraphicBuffer(const Parcel& data)
- : BASE(), mOwner(ownHandle), mBufferMapper(GraphicBufferMapper::get()),
- mInitCheck(NO_ERROR), mVStride(0), mIndex(-1)
-{
- // we own the handle in this case
- width = data.readInt32();
- if (width < 0) {
- width = height = stride = format = usage = 0;
- handle = 0;
- } else {
- height = data.readInt32();
- stride = data.readInt32();
- format = data.readInt32();
- usage = data.readInt32();
- handle = data.readNativeHandle();
- }
-}
-
GraphicBuffer::~GraphicBuffer()
{
if (handle) {
- if (mOwner == ownHandle) {
- native_handle_close(handle);
- native_handle_delete(const_cast<native_handle*>(handle));
- } else if (mOwner == ownData) {
- GraphicBufferAllocator& allocator(GraphicBufferAllocator::get());
- allocator.free(handle);
- }
+ free_handle();
+ }
+}
+
+void GraphicBuffer::free_handle()
+{
+ if (mOwner == ownHandle) {
+ native_handle_close(handle);
+ native_handle_delete(const_cast<native_handle*>(handle));
+ } else if (mOwner == ownData) {
+ GraphicBufferAllocator& allocator(GraphicBufferAllocator::get());
+ allocator.free(handle);
}
}
@@ -192,29 +179,83 @@
return res;
}
+size_t GraphicBuffer::getFlattenedSize() const {
+ return (8 + (handle ? handle->numInts : 0))*sizeof(int);
+}
-status_t GraphicBuffer::writeToParcel(Parcel* reply,
- android_native_buffer_t const* buffer)
+size_t GraphicBuffer::getFdCount() const {
+ return handle ? handle->numFds : 0;
+}
+
+status_t GraphicBuffer::flatten(void* buffer, size_t size,
+ int fds[], size_t count) const
{
- if (buffer == NULL)
- return BAD_VALUE;
+ size_t sizeNeeded = GraphicBuffer::getFlattenedSize();
+ if (size < sizeNeeded) return NO_MEMORY;
- if (buffer->width < 0 || buffer->height < 0)
- return BAD_VALUE;
+ size_t fdCountNeeded = GraphicBuffer::getFdCount();
+ if (count < fdCountNeeded) return NO_MEMORY;
- status_t err = NO_ERROR;
- if (buffer->handle == NULL) {
- // this buffer doesn't have a handle
- reply->writeInt32(NO_MEMORY);
- } else {
- reply->writeInt32(buffer->width);
- reply->writeInt32(buffer->height);
- reply->writeInt32(buffer->stride);
- reply->writeInt32(buffer->format);
- reply->writeInt32(buffer->usage);
- err = reply->writeNativeHandle(buffer->handle);
+ int* buf = static_cast<int*>(buffer);
+ buf[0] = 'GBFR';
+ buf[1] = width;
+ buf[2] = height;
+ buf[3] = stride;
+ buf[4] = format;
+ buf[5] = usage;
+ buf[6] = 0;
+ buf[7] = 0;
+
+ if (handle) {
+ buf[6] = handle->numFds;
+ buf[7] = handle->numInts;
+ native_handle_t const* const h = handle;
+ memcpy(fds, h->data, h->numFds*sizeof(int));
+ memcpy(&buf[8], h->data + h->numFds, h->numInts*sizeof(int));
}
- return err;
+
+ return NO_ERROR;
+}
+
+status_t GraphicBuffer::unflatten(void const* buffer, size_t size,
+ int fds[], size_t count)
+{
+ if (size < 8*sizeof(int)) return NO_MEMORY;
+
+ int const* buf = static_cast<int const*>(buffer);
+ if (buf[0] != 'GBFR') return BAD_TYPE;
+
+ const size_t numFds = buf[6];
+ const size_t numInts = buf[7];
+
+ const size_t sizeNeeded = (8 + numInts) * sizeof(int);
+ if (size < sizeNeeded) return NO_MEMORY;
+
+ size_t fdCountNeeded = 0;
+ if (count < fdCountNeeded) return NO_MEMORY;
+
+ if (handle) {
+ // free previous handle if any
+ free_handle();
+ }
+
+ if (numFds || numInts) {
+ width = buf[1];
+ height = buf[2];
+ stride = buf[3];
+ format = buf[4];
+ usage = buf[5];
+ native_handle* h = native_handle_create(numFds, numInts);
+ memcpy(h->data, fds, numFds*sizeof(int));
+ memcpy(h->data + numFds, &buf[8], numInts*sizeof(int));
+ handle = h;
+ } else {
+ width = height = stride = format = usage = 0;
+ handle = NULL;
+ }
+
+ mOwner = ownHandle;
+ return NO_ERROR;
}
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index d2cfd3b..d0eedb4 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -25,6 +25,7 @@
CallStack.cpp \
Debug.cpp \
FileMap.cpp \
+ Flattenable.cpp \
RefBase.cpp \
ResourceTypes.cpp \
SharedBuffer.cpp \
diff --git a/libs/utils/Flattenable.cpp b/libs/utils/Flattenable.cpp
new file mode 100644
index 0000000..1f2ffaa
--- /dev/null
+++ b/libs/utils/Flattenable.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2006 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/Flattenable.h>
+
+namespace android {
+
+Flattenable::~Flattenable() {
+}
+
+}; // namespace android
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 2294069..9d1d420 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -17,13 +17,16 @@
package android.media;
import android.content.ContentValues;
-import android.os.SystemProperties;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Video;
+import android.media.DecoderCapabilities;
+import android.media.DecoderCapabilities.VideoDecoder;
+import android.media.DecoderCapabilities.AudioDecoder;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
/**
* MediaScanner helper class.
@@ -98,13 +101,34 @@
sFileTypeMap.put(extension, new MediaFileType(fileType, mimeType));
sMimeTypeMap.put(mimeType, Integer.valueOf(fileType));
}
+
+ private static boolean isWMAEnabled() {
+ List<AudioDecoder> decoders = DecoderCapabilities.getAudioDecoders();
+ for (AudioDecoder decoder: decoders) {
+ if (decoder == AudioDecoder.AUDIO_DECODER_WMA) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isWMVEnabled() {
+ List<VideoDecoder> decoders = DecoderCapabilities.getVideoDecoders();
+ for (VideoDecoder decoder: decoders) {
+ if (decoder == VideoDecoder.VIDEO_DECODER_WMV) {
+ return true;
+ }
+ }
+ return false;
+ }
+
static {
addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg");
addFileType("M4A", FILE_TYPE_M4A, "audio/mp4");
addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav");
addFileType("AMR", FILE_TYPE_AMR, "audio/amr");
addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb");
- if (SystemProperties.getInt("ro.media.dec.aud.wma.enabled", 0) != 0) {
+ if (isWMAEnabled()) {
addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma");
}
addFileType("OGG", FILE_TYPE_OGG, "application/ogg");
@@ -127,7 +151,7 @@
addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp");
addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2");
addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2");
- if (SystemProperties.getInt("ro.media.dec.vid.wmv.enabled", 0) != 0) {
+ if (isWMVEnabled()) {
addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv");
addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf");
}
diff --git a/media/jni/android_media_ResampleInputStream.cpp b/media/jni/android_media_ResampleInputStream.cpp
index f248557..d965d9a 100644
--- a/media/jni/android_media_ResampleInputStream.cpp
+++ b/media/jni/android_media_ResampleInputStream.cpp
@@ -92,7 +92,7 @@
jint jNpoints) {
// safety first!
- if (nFir21 + jNpoints > BUF_SIZE) {
+ if (nFir21 + jNpoints * 2 > BUF_SIZE) {
throwException(env, "java/lang/IllegalArgumentException",
"FIR+data too long %d", nFir21 + jNpoints);
return;
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 16635d3..a6053b3 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -463,7 +463,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
return OK;
}
@@ -496,6 +499,23 @@
}
}
+ if (chunk_type == FOURCC('t', 'r', 'a', 'k')) {
+ Track *track = new Track;
+ track->next = NULL;
+ if (mLastTrack) {
+ mLastTrack->next = track;
+ } else {
+ mFirstTrack = track;
+ }
+ mLastTrack = track;
+
+ track->meta = new MetaData;
+ track->includes_expensive_metadata = false;
+ track->timescale = 0;
+ track->sampleTable = new SampleTable(mDataSource);
+ track->meta->setCString(kKeyMIMEType, "application/octet-stream");
+ }
+
off_t stop_offset = *offset + chunk_size;
*offset = data_offset;
while (*offset < stop_offset) {
@@ -504,9 +524,18 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
- if (chunk_type == FOURCC('m', 'o', 'o', 'v')) {
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
+
+ if (chunk_type == FOURCC('t', 'r', 'a', 'k')) {
+ status_t err = verifyTrack(mLastTrack);
+
+ if (err != OK) {
+ return err;
+ }
+ } else if (chunk_type == FOURCC('m', 'o', 'o', 'v')) {
mHaveMetadata = true;
return UNKNOWN_ERROR; // Return a dummy error.
@@ -516,7 +545,9 @@
case FOURCC('t', 'k', 'h', 'd'):
{
- CHECK(chunk_data_size >= 4);
+ if (chunk_data_size < 4) {
+ return ERROR_MALFORMED;
+ }
uint8_t version;
if (mDataSource->readAt(data_offset, &version, 1) < 1) {
@@ -562,21 +593,6 @@
height = U32_AT(&buffer[80]);
}
- Track *track = new Track;
- track->next = NULL;
- if (mLastTrack) {
- mLastTrack->next = track;
- } else {
- mFirstTrack = track;
- }
- mLastTrack = track;
-
- track->meta = new MetaData;
- track->includes_expensive_metadata = false;
- track->timescale = 0;
- track->sampleTable = new SampleTable(mDataSource);
- track->meta->setCString(kKeyMIMEType, "application/octet-stream");
-
*offset += chunk_size;
break;
}
@@ -670,7 +686,10 @@
}
uint8_t buffer[8];
- CHECK(chunk_data_size >= (off_t)sizeof(buffer));
+ if (chunk_data_size < (off_t)sizeof(buffer)) {
+ return ERROR_MALFORMED;
+ }
+
if (mDataSource->readAt(
data_offset, buffer, 8) < 8) {
return ERROR_IO;
@@ -696,7 +715,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
break;
}
@@ -748,7 +770,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
break;
}
@@ -792,7 +817,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
break;
}
@@ -942,7 +970,10 @@
case FOURCC('m', 'e', 't', 'a'):
{
uint8_t buffer[4];
- CHECK(chunk_data_size >= (off_t)sizeof(buffer));
+ if (chunk_data_size < (off_t)sizeof(buffer)) {
+ return ERROR_MALFORMED;
+ }
+
if (mDataSource->readAt(
data_offset, buffer, 4) < 4) {
return ERROR_IO;
@@ -961,7 +992,10 @@
return err;
}
}
- CHECK_EQ(*offset, stop_offset);
+
+ if (*offset != stop_offset) {
+ return ERROR_MALFORMED;
+ }
break;
}
@@ -995,8 +1029,9 @@
int64_t creationTime;
if (header[0] == 1) {
creationTime = U64_AT(&header[4]);
+ } else if (header[0] != 0) {
+ return ERROR_MALFORMED;
} else {
- CHECK_EQ(header[0], 0);
creationTime = U32_AT(&header[4]);
}
@@ -1174,6 +1209,30 @@
track->meta, mDataSource, track->timescale, track->sampleTable);
}
+// static
+status_t MPEG4Extractor::verifyTrack(Track *track) {
+ const char *mime;
+ CHECK(track->meta->findCString(kKeyMIMEType, &mime));
+
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
+ if (!track->meta->findData(kKeyAVCC, &type, &data, &size)
+ || type != kTypeAVCC) {
+ return ERROR_MALFORMED;
+ }
+ } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4)
+ || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) {
+ if (!track->meta->findData(kKeyESDS, &type, &data, &size)
+ || type != kTypeESDS) {
+ return ERROR_MALFORMED;
+ }
+ }
+
+ return OK;
+}
+
status_t MPEG4Extractor::updateAudioTrackInfoFromESDS_MPEG4Audio(
const void *esds_data, size_t esds_size) {
ESDS esds(esds_data, esds_size);
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 6cf7cff..4075ec1 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -298,6 +298,10 @@
quirks |= kRequiresAllocateBufferOnOutputPorts;
}
+ if (!strcmp(componentName, "OMX.TI.Video.Decoder")) {
+ quirks |= kInputBufferSizesAreBogus;
+ }
+
return quirks;
}
@@ -561,7 +565,8 @@
mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
CHECK_EQ(err, OK);
- if (def.nBufferSize < size) {
+ if ((portIndex == kPortIndexInput && (mQuirks & kInputBufferSizesAreBogus))
+ || (def.nBufferSize < size)) {
def.nBufferSize = size;
}
@@ -574,7 +579,12 @@
CHECK_EQ(err, OK);
// Make sure the setting actually stuck.
- CHECK(def.nBufferSize >= size);
+ if (portIndex == kPortIndexInput
+ && (mQuirks & kInputBufferSizesAreBogus)) {
+ CHECK_EQ(def.nBufferSize, size);
+ } else {
+ CHECK(def.nBufferSize >= size);
+ }
}
status_t OMXCodec::setVideoPortFormatType(
diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
index 3a63e88..9d35e0c 100644
--- a/media/libstagefright/include/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -68,6 +68,8 @@
status_t updateAudioTrackInfoFromESDS_MPEG4Audio(
const void *esds_data, size_t esds_size);
+ static status_t verifyTrack(Track *track);
+
MPEG4Extractor(const MPEG4Extractor &);
MPEG4Extractor &operator=(const MPEG4Extractor &);
};
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 9ca060d..ff8757d 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -401,6 +401,33 @@
////////////////////////////////////////////////////////////////////////////////
+struct SharedVideoRenderer : public VideoRenderer {
+ SharedVideoRenderer(void *libHandle, VideoRenderer *obj)
+ : mLibHandle(libHandle),
+ mObj(obj) {
+ }
+
+ virtual ~SharedVideoRenderer() {
+ delete mObj;
+ mObj = NULL;
+
+ dlclose(mLibHandle);
+ mLibHandle = NULL;
+ }
+
+ virtual void render(
+ const void *data, size_t size, void *platformPrivate) {
+ return mObj->render(data, size, platformPrivate);
+ }
+
+private:
+ void *mLibHandle;
+ VideoRenderer *mObj;
+
+ SharedVideoRenderer(const SharedVideoRenderer &);
+ SharedVideoRenderer &operator=(const SharedVideoRenderer &);
+};
+
sp<IOMXRenderer> OMX::createRenderer(
const sp<ISurface> &surface,
const char *componentName,
@@ -411,11 +438,7 @@
VideoRenderer *impl = NULL;
- static void *libHandle = NULL;
-
- if (!libHandle) {
- libHandle = dlopen("libstagefrighthw.so", RTLD_NOW);
- }
+ void *libHandle = dlopen("libstagefrighthw.so", RTLD_NOW);
if (libHandle) {
typedef VideoRenderer *(*CreateRendererFunc)(
@@ -434,6 +457,16 @@
if (func) {
impl = (*func)(surface, componentName, colorFormat,
displayWidth, displayHeight, encodedWidth, encodedHeight);
+
+ if (impl) {
+ impl = new SharedVideoRenderer(libHandle, impl);
+ libHandle = NULL;
+ }
+ }
+
+ if (libHandle) {
+ dlclose(libHandle);
+ libHandle = NULL;
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 5b616b3..8036e52 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -76,7 +76,7 @@
// database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
// is properly propagated through your change. Not doing so will result in a loss of user
// settings.
- private static final int DATABASE_VERSION = 50;
+ private static final int DATABASE_VERSION = 51;
private Context mContext;
@@ -642,6 +642,25 @@
upgradeVersion = 50;
}
+ if (upgradeVersion == 50) {
+ /*
+ * New settings for set install location UI.
+ */
+ db.beginTransaction();
+ try {
+ SQLiteStatement stmt = db.compileStatement("INSERT INTO system(name,value)"
+ + " VALUES(?,?);");
+ loadBooleanSetting(stmt, Settings.System.SET_INSTALL_LOCATION,
+ R.bool.set_install_location);
+ stmt.close();
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ upgradeVersion = 51;
+ }
+
if (upgradeVersion != currentVersion) {
Log.w(TAG, "Got stuck trying to upgrade from version " + upgradeVersion
+ ", must wipe the settings provider");
diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java
index 606b589..027f35c 100644
--- a/services/java/com/android/server/DockObserver.java
+++ b/services/java/com/android/server/DockObserver.java
@@ -416,7 +416,7 @@
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
LOCATION_UPDATE_MS, LOCATION_UPDATE_DISTANCE_METER, mLocationListener);
retrieveLocation();
- if (mLocation != null) {
+ if (mCarModeEnabled && mLocation != null && mNightMode == MODE_NIGHT_AUTO) {
try {
DockObserver.this.updateTwilight();
} catch (RemoteException e) {
@@ -462,8 +462,8 @@
if (location == null) {
Time currentTime = new Time();
currentTime.set(System.currentTimeMillis());
- double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * currentTime.gmtoff
- - (currentTime.isDst > 0 ? 3600 : 0);
+ double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE *
+ (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0));
location = new Location("fake");
location.setLongitude(lngOffset);
location.setLatitude(59.95);
@@ -587,20 +587,26 @@
}
// schedule next update
- final int mLastTwilightState = tw.mState;
- // add some extra time to be on the save side.
- long nextUpdate = DateUtils.MINUTE_IN_MILLIS;
- if (currentTime > tw.mSunset) {
- // next update should be on the following day
- tw.calculateTwilight(currentTime
- + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
- mLocation.getLongitude());
- }
-
- if (mLastTwilightState == TwilightCalculator.NIGHT) {
- nextUpdate += tw.mSunrise;
+ long nextUpdate = 0;
+ if (tw.mSunrise == -1 || tw.mSunset == -1) {
+ // In the case the day or night never ends the update is scheduled 12 hours later.
+ nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS;
} else {
- nextUpdate += tw.mSunset;
+ final int mLastTwilightState = tw.mState;
+ // add some extra time to be on the save side.
+ nextUpdate += DateUtils.MINUTE_IN_MILLIS;
+ if (currentTime > tw.mSunset) {
+ // next update should be on the following day
+ tw.calculateTwilight(currentTime
+ + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(),
+ mLocation.getLongitude());
+ }
+
+ if (mLastTwilightState == TwilightCalculator.NIGHT) {
+ nextUpdate += tw.mSunrise;
+ } else {
+ nextUpdate += tw.mSunset;
+ }
}
Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE);
@@ -609,8 +615,11 @@
mAlarmManager.cancel(pendingIntent);
mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent);
- // set current mode
- setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
+ // Make sure that we really set the new mode only if we're in car mode and
+ // automatic switching is enables.
+ if (mCarModeEnabled && mNightMode == MODE_NIGHT_AUTO) {
+ setMode(Configuration.UI_MODE_TYPE_CAR, nightMode << 4);
+ }
}
}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index dd351be..c0dcdf9 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -485,14 +485,17 @@
super();
mContext = context;
- Thread thread = new Thread(null, this, "LocationManagerService");
- thread.start();
-
if (LOCAL_LOGV) {
Log.v(TAG, "Constructed LocationManager Service");
}
}
+ void systemReady() {
+ // we defer starting up the service until the system is ready
+ Thread thread = new Thread(null, this, "LocationManagerService");
+ thread.start();
+ }
+
private void initialize() {
// Create a wake lock, needs to be done before calling loadProviders() below
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 4fdcd59..4a7fc9c 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -5704,6 +5704,18 @@
// remove permissions associated with package
mSettings.updateSharedUserPermsLP(deletedPs, mGlobalGids);
}
+ if (deletedPs != null) {
+ // remove from preferred activities.
+ ArrayList<PreferredActivity> removed = new ArrayList<PreferredActivity>();
+ for (PreferredActivity pa : mSettings.mPreferredActivities.filterSet()) {
+ if (pa.mActivity.getPackageName().equals(deletedPs.name)) {
+ removed.add(pa);
+ }
+ }
+ for (PreferredActivity pa : removed) {
+ mSettings.mPreferredActivities.removeFilter(pa);
+ }
+ }
// Save settings now
mSettings.writeLP();
}
@@ -7461,9 +7473,9 @@
Log.w(TAG, "Trying to update system app code path from " +
p.codePathString + " to " + codePath.toString());
} else {
- // Let the app continue with previous uid if code path changes.
- reportSettingsProblem(Log.WARN,
- "Package " + name + " codePath changed from " + p.codePath
+ // Just a change in the code path is not an issue, but
+ // let's log a message about it.
+ Log.i(TAG, "Package " + name + " codePath changed from " + p.codePath
+ " to " + codePath + "; Retaining data and using new");
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 38df02f..1f46faf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -209,6 +209,7 @@
AppWidgetService appWidget = null;
NotificationManagerService notification = null;
WallpaperManagerService wallpaper = null;
+ LocationManagerService location = null;
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
@@ -303,8 +304,8 @@
try {
Log.i(TAG, "Location Manager");
- ServiceManager.addService(Context.LOCATION_SERVICE,
- new LocationManagerService(context));
+ location = new LocationManagerService(context);
+ ServiceManager.addService(Context.LOCATION_SERVICE, location);
} catch (Throwable e) {
Log.e(TAG, "Failure starting Location Manager", e);
}
@@ -444,6 +445,7 @@
final WallpaperManagerService wallpaperF = wallpaper;
final InputMethodManagerService immF = imm;
final RecognitionManagerService recognitionF = recognition;
+ final LocationManagerService locationF = location;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@@ -467,6 +469,7 @@
if (appWidgetF != null) appWidgetF.systemReady(safeMode);
if (wallpaperF != null) wallpaperF.systemReady();
if (immF != null) immF.systemReady();
+ if (locationF != null) locationF.systemReady();
}
});
diff --git a/services/java/com/android/server/TwilightCalculator.java b/services/java/com/android/server/TwilightCalculator.java
index a8f67d8..a5c93b5 100644
--- a/services/java/com/android/server/TwilightCalculator.java
+++ b/services/java/com/android/server/TwilightCalculator.java
@@ -46,10 +46,16 @@
// Java time on Jan 1, 2000 12:00 UTC.
private static final long UTC_2000 = 946728000000L;
- /** Time of sunset (civil twilight) in milliseconds. */
+ /**
+ * Time of sunset (civil twilight) in milliseconds or -1 in the case the day
+ * or night never ends.
+ */
public long mSunset;
- /** Time of sunrise (civil twilight) in milliseconds. */
+ /**
+ * Time of sunrise (civil twilight) in milliseconds or -1 in the case the
+ * day or night never ends.
+ */
public long mSunrise;
/** Current state */
@@ -85,10 +91,24 @@
double solarDec = Math.asin(FloatMath.sin(solarLng) * FloatMath.sin(OBLIQUITY));
final double latRad = latiude * DEGREES_TO_RADIANS;
- float hourAngle = (float) (Math
- .acos((FloatMath.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
- * Math.sin(solarDec))
- / (Math.cos(latRad) * Math.cos(solarDec))) / (2 * Math.PI));
+
+ double cosHourAngle = (FloatMath.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
+ * Math.sin(solarDec)) / (Math.cos(latRad) * Math.cos(solarDec));
+ // The day or night never ends for the given date and location, if this value is out of
+ // range.
+ if (cosHourAngle >= 1) {
+ mState = NIGHT;
+ mSunset = -1;
+ mSunrise = -1;
+ return;
+ } else if (cosHourAngle <= -1) {
+ mState = DAY;
+ mSunset = -1;
+ mSunrise = -1;
+ return;
+ }
+
+ float hourAngle = (float) (Math.acos(cosHourAngle) / (2 * Math.PI));
mSunset = Math.round((solarTransitJ2000 + hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
mSunrise = Math.round((solarTransitJ2000 - hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 5b37f47..4169cc5 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -135,6 +135,7 @@
static final boolean DEBUG_VISBILITY = localLOGV || false;
static final boolean DEBUG_PROCESSES = localLOGV || false;
static final boolean DEBUG_PROVIDER = localLOGV || false;
+ static final boolean DEBUG_URI_PERMISSION = localLOGV || false;
static final boolean DEBUG_USER_LEAVING = localLOGV || false;
static final boolean DEBUG_RESULTS = localLOGV || false;
static final boolean DEBUG_BACKUP = localLOGV || false;
@@ -968,6 +969,7 @@
static final int PROC_START_TIMEOUT_MSG = 20;
static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21;
static final int KILL_APPLICATION_MSG = 22;
+ static final int FINALIZE_PENDING_INTENT_MSG = 23;
AlertDialog mUidAlert;
@@ -1189,6 +1191,9 @@
forceStopPackageLocked(pkg, uid, restart, false, true);
}
} break;
+ case FINALIZE_PENDING_INTENT_MSG: {
+ ((PendingIntentRecord)msg.obj).completeFinalize();
+ } break;
}
}
};
@@ -3599,10 +3604,18 @@
}
synchronized(this) {
+ int callingPid;
+ int callingUid;
+ if (caller == null) {
+ callingPid = Binder.getCallingPid();
+ callingUid = Binder.getCallingUid();
+ } else {
+ callingPid = callingUid = -1;
+ }
final long origId = Binder.clearCallingIdentity();
int res = startActivityLocked(caller, intent, resolvedType,
grantedUriPermissions, grantedMode, aInfo,
- resultTo, resultWho, requestCode, -1, -1,
+ resultTo, resultWho, requestCode, callingPid, callingUid,
onlyIfNeeded, componentSpecified);
Binder.restoreCallingIdentity(origId);
return res;
@@ -6166,10 +6179,15 @@
return;
}
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Requested grant " + targetPkg + " permission to " + uri);
+
final IPackageManager pm = ActivityThread.getPackageManager();
// If this is not a content: uri, we can't do anything with it.
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Can't grant URI permission for non-content URI: " + uri);
return;
}
@@ -6195,6 +6213,8 @@
try {
targetUid = pm.getPackageUid(targetPkg);
if (targetUid < 0) {
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Can't grant URI permission no uid for: " + targetPkg);
return;
}
} catch (RemoteException ex) {
@@ -6204,17 +6224,12 @@
// First... does the target actually need this permission?
if (checkHoldingPermissionsLocked(pm, pi, targetUid, modeFlags)) {
// No need to grant the target this permission.
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Target " + targetPkg + " already has full permission to " + uri);
return;
}
- // Second... maybe someone else has already granted the
- // permission?
- if (checkUriPermissionLocked(uri, targetUid, modeFlags)) {
- // No need to grant the target this permission.
- return;
- }
-
- // Third... is the provider allowing granting of URI permissions?
+ // Second... is the provider allowing granting of URI permissions?
if (!pi.grantUriPermissions) {
throw new SecurityException("Provider " + pi.packageName
+ "/" + pi.name
@@ -6239,7 +6254,7 @@
}
}
- // Fourth... does the caller itself have permission to access
+ // Third... does the caller itself have permission to access
// this uri?
if (!checkHoldingPermissionsLocked(pm, pi, callingUid, modeFlags)) {
if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
@@ -6252,6 +6267,9 @@
// to the uri, and the target doesn't. Let's now give this to
// the target.
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Granting " + targetPkg + " permission to " + uri);
+
HashMap<Uri, UriPermission> targetUris
= mGrantedUriPermissions.get(targetUid);
if (targetUris == null) {
@@ -6325,6 +6343,8 @@
HashMap<Uri, UriPermission> perms
= mGrantedUriPermissions.get(perm.uid);
if (perms != null) {
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Removing " + perm.uid + " permission to " + perm.uri);
perms.remove(perm.uri);
if (perms.size() == 0) {
mGrantedUriPermissions.remove(perm.uid);
@@ -6364,6 +6384,9 @@
return;
}
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Revoking all granted permissions to " + uri);
+
final IPackageManager pm = ActivityThread.getPackageManager();
final String authority = uri.getAuthority();
@@ -6422,6 +6445,8 @@
continue toploop;
}
}
+ if (DEBUG_URI_PERMISSION) Log.v(TAG,
+ "Revoking " + perm.uid + " permission to " + perm.uri);
perm.clearModes(modeFlags);
if (perm.modeFlags == 0) {
it.remove();
diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java
index b3086d5..94b5f56 100644
--- a/services/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/java/com/android/server/am/PendingIntentRecord.java
@@ -262,17 +262,26 @@
}
protected void finalize() throws Throwable {
- if (!canceled) {
- synchronized(owner) {
- WeakReference<PendingIntentRecord> current =
- owner.mIntentSenderRecords.get(key);
- if (current == ref) {
- owner.mIntentSenderRecords.remove(key);
- }
+ try {
+ if (!canceled) {
+ owner.mHandler.sendMessage(owner.mHandler.obtainMessage(
+ ActivityManagerService.FINALIZE_PENDING_INTENT_MSG, this));
}
+ } finally {
+ super.finalize();
}
}
+ public void completeFinalize() {
+ synchronized(owner) {
+ WeakReference<PendingIntentRecord> current =
+ owner.mIntentSenderRecords.get(key);
+ if (current == ref) {
+ owner.mIntentSenderRecords.remove(key);
+ }
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("uid="); pw.print(uid);
pw.print(" packageName="); pw.print(key.packageName);
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 2f2cc32..5a02c40 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -269,8 +269,12 @@
inm.enqueueNotification(localPackageName, localForegroundId,
localForegroundNoti, outId);
} catch (RuntimeException e) {
- Log.w(ActivityManagerService.TAG, "Error showing notification for service",
- e);
+ Log.w(ActivityManagerService.TAG,
+ "Error showing notification for service", e);
+ // If it gave us a garbage notification, it doesn't
+ // get to be foreground.
+ ams.setServiceForeground(name, ServiceRecord.this,
+ localForegroundId, null, true);
} catch (RemoteException e) {
}
}
@@ -293,8 +297,8 @@
try {
inm.cancelNotification(localPackageName, localForegroundId);
} catch (RuntimeException e) {
- Log.w(ActivityManagerService.TAG, "Error canceling notification for"
- + " service", e);
+ Log.w(ActivityManagerService.TAG,
+ "Error canceling notification for service", e);
} catch (RemoteException e) {
}
}
diff --git a/services/java/com/android/server/status/StorageNotification.java b/services/java/com/android/server/status/StorageNotification.java
index 3b79049..e0b288d 100644
--- a/services/java/com/android/server/status/StorageNotification.java
+++ b/services/java/com/android/server/status/StorageNotification.java
@@ -36,6 +36,7 @@
import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.StorageResultCode;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@@ -46,6 +47,8 @@
public class StorageNotification extends StorageEventListener {
private static final String TAG = "StorageNotification";
+ private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true;
+
/**
* Binder context for this service
*/
@@ -239,12 +242,28 @@
Intent intent = new Intent();
intent.setClass(mContext, com.android.server.status.UsbStorageActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final boolean adbOn = 1 == Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ADB_ENABLED,
+ 0);
+
PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
setUsbStorageNotification(
com.android.internal.R.string.usb_storage_notification_title,
com.android.internal.R.string.usb_storage_notification_message,
com.android.internal.R.drawable.stat_sys_data_usb,
false, true, pi);
+
+ if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) {
+ // We assume that developers don't want to enable UMS every
+ // time they attach a device to a USB host. The average user,
+ // however, is looking to charge the phone (in which case this
+ // is harmless) or transfer files (in which case this coaches
+ // the user about how to complete that task and saves several
+ // steps).
+ mContext.startActivity(intent);
+ }
} else {
setUsbStorageNotification(0, 0, 0, false, false, null);
}
diff --git a/telephony/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/telephony/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
index 31cf6a7..9f8e57f 100644
--- a/telephony/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -22,10 +22,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.ServiceManager;
-import android.telephony.PhoneNumberUtils;
-import android.util.Log;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -37,7 +34,7 @@
protected PhoneBase phone;
protected AdnRecordCache adnCache;
- protected Object mLock = new Object();
+ protected final Object mLock = new Object();
protected int recordSize[];
protected boolean success;
protected List<AdnRecord> records;
@@ -80,8 +77,7 @@
ar = (AsyncResult)msg.obj;
synchronized (mLock) {
if (ar.exception == null) {
- records = (List<AdnRecord>)
- ((ArrayList<AdnRecord>) ar.result);
+ records = (List<AdnRecord>) ar.result;
} else {
if(DBG) logd("Cannot load ADN records");
if (records != null) {
diff --git a/telephony/java/com/android/internal/telephony/PhoneSubInfo.java b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
index 19900c8..1ac2da3 100644
--- a/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
+++ b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
@@ -39,6 +39,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
Log.d(LOG_TAG, "PhoneSubInfo finalized");
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java
index 78e89d5..6e12f24a 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimPhoneBookInterfaceManager.java
@@ -16,22 +16,10 @@
package com.android.internal.telephony.cdma;
-import android.content.pm.PackageManager;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
-import android.os.ServiceManager;
-import android.telephony.PhoneNumberUtils;
import android.util.Log;
-import com.android.internal.telephony.AdnRecord;
-import com.android.internal.telephony.AdnRecordCache;
import com.android.internal.telephony.IccPhoneBookInterfaceManager;
-import com.android.internal.telephony.PhoneProxy;
-
-import java.util.ArrayList;
-import java.util.List;
/**
* RuimPhoneBookInterfaceManager to provide an inter-process communication to
@@ -42,20 +30,6 @@
public class RuimPhoneBookInterfaceManager extends IccPhoneBookInterfaceManager {
static final String LOG_TAG = "CDMA";
-
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- AsyncResult ar;
-
- switch(msg.what) {
- default:
- mBaseHandler.handleMessage(msg);
- break;
- }
- }
- };
-
public RuimPhoneBookInterfaceManager(CDMAPhone phone) {
super(phone);
adnCache = phone.mRuimRecords.getAdnCache();
@@ -67,6 +41,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
if(DBG) Log.d(LOG_TAG, "RuimPhoneBookInterfaceManager finalized");
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
index 9439359..cfcfd98 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java
@@ -30,6 +30,7 @@
import com.android.internal.telephony.SmsRawData;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
@@ -89,6 +90,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
if(DBG) Log.d(LOG_TAG, "RuimSmsInterfaceManager finalized");
}
@@ -143,7 +149,7 @@
public boolean copyMessageToIccEf(int status, byte[] pdu, byte[] smsc) {
//NOTE smsc not used in RUIM
if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
- "pdu=("+ pdu + ")");
+ "pdu=("+ Arrays.toString(pdu) + ")");
enforceReceiveAndSend("Copying message to RUIM");
synchronized(mLock) {
mSuccess = false;
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
index 076da6b..feb508a 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimPhoneBookInterfaceManager.java
@@ -16,22 +16,10 @@
package com.android.internal.telephony.gsm;
-import android.content.pm.PackageManager;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
-import android.os.ServiceManager;
-import android.telephony.PhoneNumberUtils;
import android.util.Log;
-import com.android.internal.telephony.AdnRecord;
-import com.android.internal.telephony.AdnRecordCache;
import com.android.internal.telephony.IccPhoneBookInterfaceManager;
-import com.android.internal.telephony.PhoneProxy;
-
-import java.util.ArrayList;
-import java.util.List;
/**
* SimPhoneBookInterfaceManager to provide an inter-process communication to
@@ -42,20 +30,6 @@
public class SimPhoneBookInterfaceManager extends IccPhoneBookInterfaceManager {
static final String LOG_TAG = "GSM";
-
- Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- AsyncResult ar;
-
- switch(msg.what) {
- default:
- mBaseHandler.handleMessage(msg);
- break;
- }
- }
- };
-
public SimPhoneBookInterfaceManager(GSMPhone phone) {
super(phone);
adnCache = phone.mSIMRecords.getAdnCache();
@@ -67,6 +41,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
if(DBG) Log.d(LOG_TAG, "SimPhoneBookInterfaceManager finalized");
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
index 875d8d0..2028ca4 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java
@@ -25,10 +25,10 @@
import com.android.internal.telephony.IccConstants;
import com.android.internal.telephony.IccSmsInterfaceManager;
import com.android.internal.telephony.IccUtils;
-import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.SmsRawData;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
@@ -65,8 +65,7 @@
ar = (AsyncResult)msg.obj;
synchronized (mLock) {
if (ar.exception == null) {
- mSms = (List<SmsRawData>)
- buildValidRawData((ArrayList<byte[]>) ar.result);
+ mSms = buildValidRawData((ArrayList<byte[]>) ar.result);
} else {
if(DBG) log("Cannot load Sms records");
if (mSms != null)
@@ -88,6 +87,11 @@
}
protected void finalize() {
+ try {
+ super.finalize();
+ } catch (Throwable throwable) {
+ Log.e(LOG_TAG, "Error while finalizing:", throwable);
+ }
if(DBG) Log.d(LOG_TAG, "SimSmsInterfaceManager finalized");
}
@@ -106,7 +110,7 @@
updateMessageOnIccEf(int index, int status, byte[] pdu) {
if (DBG) log("updateMessageOnIccEf: index=" + index +
" status=" + status + " ==> " +
- "("+ pdu + ")");
+ "("+ Arrays.toString(pdu) + ")");
enforceReceiveAndSend("Updating message on SIM");
synchronized(mLock) {
mSuccess = false;
@@ -118,7 +122,7 @@
mPhone.mCM.deleteSmsOnSim(index, response);
} else {
byte[] record = makeSmsRecordData(status, pdu);
- ((SIMFileHandler)mPhone.getIccFileHandler()).updateEFLinearFixed(
+ mPhone.getIccFileHandler().updateEFLinearFixed(
IccConstants.EF_SMS,
index, record, null, response);
}
@@ -142,7 +146,8 @@
*/
public boolean copyMessageToIccEf(int status, byte[] pdu, byte[] smsc) {
if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
- "pdu=("+ pdu + "), smsm=(" + smsc +")");
+ "pdu=("+ Arrays.toString(pdu) +
+ "), smsm=(" + Arrays.toString(smsc) +")");
enforceReceiveAndSend("Copying message to SIM");
synchronized(mLock) {
mSuccess = false;
@@ -175,8 +180,7 @@
"Reading messages from SIM");
synchronized(mLock) {
Message response = mHandler.obtainMessage(EVENT_LOAD_DONE);
- ((SIMFileHandler)mPhone.getIccFileHandler()).loadEFLinearFixedAll(IccConstants.EF_SMS,
- response);
+ mPhone.getIccFileHandler().loadEFLinearFixedAll(IccConstants.EF_SMS, response);
try {
mLock.wait();
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
index 8983612..634d683 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java
@@ -339,14 +339,7 @@
this.mTestList = new Vector<String>();
// Read settings
- try {
- this.mTestPathPrefix =
- (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getCanonicalPath();
- } catch (IOException e) {
- Log.e(LOGTAG, "Cannot find test path prefix: " + e.getMessage());
- return;
- }
-
+ this.mTestPathPrefix = (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getAbsolutePath();
this.mRebaselineResults = runner.mRebaseline;
int timeout = runner.mTimeoutInMillis;
@@ -395,11 +388,7 @@
if (runner.mTestPath != null) {
test_path += runner.mTestPath;
}
- try {
- test_path = new File(test_path).getCanonicalPath();
- } catch (IOException e) {
- Log.e("LayoutTestsAutoTest", "Cannot get cannonical path " + e.getMessage());
- }
+ test_path = new File(test_path).getAbsolutePath();
Log.v("LayoutTestsAutoTest", " Test path : " + test_path);
return test_path;
diff --git a/tests/LocationTracker/Android.mk b/tests/LocationTracker/Android.mk
new file mode 100644
index 0000000..b142d22
--- /dev/null
+++ b/tests/LocationTracker/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := LocationTracker
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/LocationTracker/AndroidManifest.xml b/tests/LocationTracker/AndroidManifest.xml
new file mode 100644
index 0000000..dc7ea99
--- /dev/null
+++ b/tests/LocationTracker/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.locationtracker">
+
+ <!-- Permissions for the Location Service -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Permission for wifi -->
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+
+ <!-- give the location tracker ability to induce device insomnia -->
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+ <!-- Permission for SD card -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application android:label="@string/app_label">
+ <activity android:name="TrackerActivity" android:label="Location Tracker">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <service android:name=".TrackerService" />
+ <activity android:label="@string/settings_menu" android:name="SettingsActivity" />
+ <provider android:name=".data.TrackerProvider"
+ android:authorities="com.android.locationtracker" />
+ </application>
+
+</manifest>
diff --git a/tests/LocationTracker/res/layout/entrylist_item.xml b/tests/LocationTracker/res/layout/entrylist_item.xml
new file mode 100644
index 0000000..8187677
--- /dev/null
+++ b/tests/LocationTracker/res/layout/entrylist_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2006-2008 Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/entrylist_item"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+/>
diff --git a/tests/LocationTracker/res/menu/menu.xml b/tests/LocationTracker/res/menu/menu.xml
new file mode 100644
index 0000000..94c589a
--- /dev/null
+++ b/tests/LocationTracker/res/menu/menu.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+* Copyright (c) 2008 Google Inc.
+*
+* 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.
+*/
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/start_service_menu"
+ android:title="@string/start_service_menu" />
+ <item android:id="@+id/stop_service_menu"
+ android:title="@string/stop_service_menu" />
+ <item android:id="@+id/settings_menu"
+ android:title="@string/settings_menu" />
+ <item android:id="@+id/export_sub_menu"
+ android:title="@string/export_sub_menu">
+ <menu>
+ <item android:id="@+id/export_kml"
+ android:title="@string/export_kml" />
+ <item android:id="@+id/export_csv"
+ android:title="@string/export_csv" />
+ </menu>
+ </item>
+ <item android:title="@string/clear_data"
+ android:id="@+id/clear_data_menu"/>
+</menu>
diff --git a/tests/LocationTracker/res/values/strings.xml b/tests/LocationTracker/res/values/strings.xml
new file mode 100644
index 0000000..bb3ea86
--- /dev/null
+++ b/tests/LocationTracker/res/values/strings.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 Google Inc.
+*
+* 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.
+*/
+-->
+
+<resources>
+ <string name="start_service_menu">Start Service</string>
+ <string name="stop_service_menu">Stop Service</string>
+ <string name="settings_menu">Settings</string>
+ <string name="update_preference">Update frequency</string>
+ <string name="title_mintime_preference">Minimum update time</string>
+ <string name="summary_mintime_preference">The suggested minimum time interval for location updates, in seconds</string>
+ <string name="dialog_title_mintime_preference">Minimum update time</string>
+ <string name="title_mindistance_preference">Minimum distance</string>
+ <string name="summary_mindistance_preference">Minimum distance interval for location updates, in meters</string>
+ <string name="dialog_title_mindistance_preference">Minimum distance</string>
+ <string name="provider_preferences">Location providers</string>
+ <string name="title_network_preference">Network location</string>
+ <string name="summary_network_preference">Listen for updates to network location (Wi-Fi/cellid)</string>
+ <string name="title_gps_preference">GPS location</string>
+ <string name="summary_gps_preference">Listen for updates to GPS location</string>
+ <string name="title_signal_preference">Signal strength</string>
+ <string name="summary_signal_preference">Listen for updates to signal strength</string>
+ <string name="advanced_preferences">Advanced</string>
+ <string name="title_advanced_log_preference">Location debug logging</string>
+ <string name="summary_advanced_preference">Logs detailed location data, only relevant for location/test engineers</string>
+ <string name="app_label">Location Tracker</string>
+ <string name="export_sub_menu">Export\u2026</string>
+ <string name="export_kml">Export As KML</string>
+ <string name="export_csv">Export As CSV</string>
+ <string name="clear_data">Clear data</string>
+ <string name="delete_confirm">All current tracking data will be deleted.</string>
+ <string name="confirm_title">Clear data</string>
+</resources>
diff --git a/tests/LocationTracker/res/xml/preferences.xml b/tests/LocationTracker/res/xml/preferences.xml
new file mode 100755
index 0000000..b57837f
--- /dev/null
+++ b/tests/LocationTracker/res/xml/preferences.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 Google Inc.
+
+ 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.
+-->
+
+<!-- The Location preferences UI -->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <PreferenceCategory android:title="@string/update_preference">
+ <EditTextPreference android:key="mintime_preference"
+ android:defaultValue="0"
+ android:title="@string/title_mintime_preference"
+ android:summary="@string/summary_mintime_preference"
+ android:dialogTitle="@string/dialog_title_mintime_preference" />
+
+ <EditTextPreference android:key="mindistance_preference"
+ android:defaultValue="0"
+ android:title="@string/title_mindistance_preference"
+ android:summary="@string/summary_mindistance_preference"
+ android:dialogTitle="@string/dialog_title_mindistance_preference" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory android:title="@string/provider_preferences">
+
+ <CheckBoxPreference android:key="network_preference"
+ android:defaultValue="true"
+ android:title="@string/title_network_preference"
+ android:summary="@string/summary_network_preference" />
+
+ <CheckBoxPreference android:key="gps_preference"
+ android:defaultValue="true"
+ android:title="@string/title_gps_preference"
+ android:summary="@string/summary_gps_preference" />
+
+ <CheckBoxPreference android:key="signal_preference"
+ android:defaultValue="false"
+ android:title="@string/title_signal_preference"
+ android:summary="@string/summary_signal_preference" />
+
+ </PreferenceCategory>
+
+ <PreferenceCategory android:title="@string/advanced_preferences">
+
+ <CheckBoxPreference android:key="advanced_log_preference"
+ android:defaultValue="false"
+ android:title="@string/title_advanced_log_preference"
+ android:summary="@string/summary_advanced_preference" />
+ </PreferenceCategory>
+
+</PreferenceScreen>
diff --git a/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
new file mode 100755
index 0000000..c5b2432
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+/**
+ * Settings preference screen for location tracker
+ */
+public class SettingsActivity extends PreferenceActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.preferences);
+ }
+
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java b/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java
new file mode 100644
index 0000000..92e7803
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker;
+
+import com.android.locationtracker.data.DateUtils;
+import com.android.locationtracker.data.TrackerDataHelper;
+import com.android.locationtracker.data.TrackerListHelper;
+
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.database.Cursor;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Activity for location tracker service
+ *
+ * Contains facilities for starting and
+ * stopping location tracker service, as well as displaying the current location
+ * data
+ *
+ * Future enhancements:
+ * - export data as dB
+ * - enable/disable "start service" and "stop service" menu items as
+ * appropriate
+ */
+public class TrackerActivity extends ListActivity {
+
+ static final String LOG_TAG = "LocationTracker";
+
+ private TrackerListHelper mDataHelper;
+
+ /**
+ * Retrieves and displays the currently logged location data from file
+ *
+ * @param icicle
+ */
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mDataHelper = new TrackerListHelper(this);
+ mDataHelper.bindListUI(R.layout.entrylist_item);
+ }
+
+ /**
+ * Builds the menu
+ *
+ * @param menu - menu to add items to
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater menuInflater = getMenuInflater();
+ menuInflater.inflate(R.menu.menu, menu);
+ return true;
+ }
+
+ /**
+ * Handles menu item selection
+ *
+ * @param item - the selected menu item
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.start_service_menu: {
+ startService(new Intent(TrackerActivity.this,
+ TrackerService.class));
+ break;
+ }
+ case R.id.stop_service_menu: {
+ stopService(new Intent(TrackerActivity.this,
+ TrackerService.class));
+ break;
+ }
+ case R.id.settings_menu: {
+ launchSettings();
+ break;
+ }
+ case R.id.export_kml: {
+ exportKML();
+ break;
+ }
+ case R.id.export_csv: {
+ exportCSV();
+ break;
+ }
+ case R.id.clear_data_menu: {
+ clearData();
+ break;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void clearData() {
+ Runnable clearAction = new Runnable() {
+ public void run() {
+ TrackerDataHelper helper =
+ new TrackerDataHelper(TrackerActivity.this);
+ helper.deleteAll();
+ }
+ };
+ showConfirm(R.string.delete_confirm, clearAction);
+ }
+
+ private void showConfirm(int textId, final Runnable onConfirmAction) {
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
+ dialogBuilder.setTitle(R.string.confirm_title);
+ dialogBuilder.setMessage(textId);
+ dialogBuilder.setPositiveButton(android.R.string.ok,
+ new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ onConfirmAction.run();
+ }
+ });
+ dialogBuilder.setNegativeButton(android.R.string.cancel,
+ new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // do nothing
+ }
+ });
+ dialogBuilder.show();
+ }
+
+ private void exportCSV() {
+ String exportFileName = getUniqueFileName("csv");
+ exportFile(null, exportFileName, new TrackerDataHelper(this,
+ TrackerDataHelper.CSV_FORMATTER));
+ }
+
+ private void exportKML() {
+ String exportFileName = getUniqueFileName(
+ LocationManager.NETWORK_PROVIDER + ".kml");
+ exportFile(LocationManager.NETWORK_PROVIDER, exportFileName,
+ new TrackerDataHelper(this, TrackerDataHelper.KML_FORMATTER));
+ exportFileName = getUniqueFileName(
+ LocationManager.GPS_PROVIDER + ".kml");
+ exportFile(LocationManager.GPS_PROVIDER, exportFileName,
+ new TrackerDataHelper(this, TrackerDataHelper.KML_FORMATTER));
+ }
+
+ private void exportFile(String tagFilter,
+ String exportFileName,
+ TrackerDataHelper trackerData) {
+ BufferedWriter exportWriter = null;
+ Cursor cursor = trackerData.query(tagFilter);
+ try {
+ exportWriter = new BufferedWriter(new FileWriter(exportFileName));
+ exportWriter.write(trackerData.getOutputHeader());
+
+ String line = null;
+
+ while ((line = trackerData.getNextOutput(cursor)) != null) {
+ exportWriter.write(line);
+ }
+ exportWriter.write(trackerData.getOutputFooter());
+ Toast.makeText(this, "Successfully exported data to " +
+ exportFileName, Toast.LENGTH_SHORT).show();
+
+ } catch (IOException e) {
+ Toast.makeText(this, "Error exporting file: " +
+ e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
+
+ Log.e(LOG_TAG, "Error exporting file", e);
+ } finally {
+ closeWriter(exportWriter);
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private void closeWriter(Writer exportWriter) {
+ if (exportWriter != null) {
+ try {
+ exportWriter.close();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "error closing file", e);
+ }
+ }
+ }
+
+ private String getUniqueFileName(String ext) {
+ File dir = new File("/sdcard/locationtracker");
+ if (!dir.exists()) {
+ dir.mkdir();
+ }
+ return "/sdcard/locationtracker/tracking-" +
+ DateUtils.getCurrentTimestamp() + "." + ext;
+ }
+
+ private void launchSettings() {
+ Intent settingsIntent = new Intent();
+ settingsIntent.setClass(this, SettingsActivity.class);
+ startActivity(settingsIntent);
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java b/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java
new file mode 100644
index 0000000..4206da7
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker;
+
+import com.android.locationtracker.data.TrackerDataHelper;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.preference.PreferenceManager;
+import android.telephony.CellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.gsm.GsmCellLocation;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Location Tracking service
+ *
+ * Records location updates for all registered location providers, and cell
+ * location updates
+ */
+public class TrackerService extends Service {
+
+ private List<LocationTrackingListener> mListeners;
+
+ private static final String LOG_TAG = TrackerActivity.LOG_TAG;
+
+ // controls which location providers to track
+ private Set<String> mTrackedProviders;
+
+ private TrackerDataHelper mTrackerData;
+
+ private TelephonyManager mTelephonyManager;
+ private Location mNetworkLocation;
+
+ // Handlers and Receivers for phone and network state
+ private NetworkStateBroadcastReceiver mNetwork;
+ private static final String CELL_PROVIDER_TAG = "cell";
+ // signal strength updates
+ private static final String SIGNAL_PROVIDER_TAG = "signal";
+ private static final String WIFI_PROVIDER_TAG = "wifi";
+ // tracking tag for data connectivity issues
+ private static final String DATA_CONN_PROVIDER_TAG = "data";
+
+ // preference constants
+ private static final String MIN_TIME_PREF = "mintime_preference";
+ private static final String MIN_DIS_PREF = "mindistance_preference";
+ private static final String GPS_PREF = "gps_preference";
+ private static final String NETWORK_PREF = "network_preference";
+ private static final String SIGNAL_PREF = "signal_preference";
+ private static final String DEBUG_PREF = "advanced_log_preference";
+
+ private PreferenceListener mPrefListener;
+
+ public TrackerService() {
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // ignore - nothing to do
+ return null;
+ }
+
+ /**
+ * registers location listeners
+ *
+ * @param intent
+ * @param startId
+ */
+ @Override
+ public void onStart(Intent intent, int startId) {
+ super.onStart(intent, startId);
+ mNetworkLocation = null;
+
+ initLocationListeners();
+ Toast.makeText(this, "Tracking service started", Toast.LENGTH_SHORT);
+ }
+
+ private synchronized void initLocationListeners() {
+ mTrackerData = new TrackerDataHelper(this);
+ LocationManager lm = getLocationManager();
+
+ mTrackedProviders = getTrackedProviders();
+
+ List<String> locationProviders = lm.getAllProviders();
+ mListeners = new ArrayList<LocationTrackingListener>(
+ locationProviders.size());
+
+ long minUpdateTime = getLocationUpdateTime();
+ float minDistance = getLocationMinDistance();
+
+ for (String providerName : locationProviders) {
+ if (mTrackedProviders.contains(providerName)) {
+ Log.d(LOG_TAG, "Adding location listener for provider " +
+ providerName);
+ if (doDebugLogging()) {
+ mTrackerData.writeEntry("init", String.format(
+ "start listening to %s : %d ms; %f meters",
+ providerName, minUpdateTime, minDistance));
+ }
+ LocationTrackingListener listener =
+ new LocationTrackingListener();
+ lm.requestLocationUpdates(providerName, minUpdateTime,
+ minDistance, listener);
+ mListeners.add(listener);
+ }
+ }
+ mTelephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
+
+ if (doDebugLogging()) {
+ // register for cell location updates
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
+
+ // Register for Network (Wifi or Mobile) updates
+ mNetwork = new NetworkStateBroadcastReceiver();
+ IntentFilter mIntentFilter;
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ Log.d(LOG_TAG, "registering receiver");
+ registerReceiver(mNetwork, mIntentFilter);
+ }
+
+ if (trackSignalStrength()) {
+ mTelephonyManager.listen(mPhoneStateListener,
+ PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ }
+
+ // register for preference changes, so we can restart listeners on
+ // pref changes
+ mPrefListener = new PreferenceListener();
+ getPreferences().registerOnSharedPreferenceChangeListener(mPrefListener);
+ }
+
+ private Set<String> getTrackedProviders() {
+ Set<String> providerSet = new HashSet<String>();
+
+ if (trackGPS()) {
+ providerSet.add(LocationManager.GPS_PROVIDER);
+ }
+ if (trackNetwork()) {
+ providerSet.add(LocationManager.NETWORK_PROVIDER);
+ }
+ return providerSet;
+ }
+
+ private SharedPreferences getPreferences() {
+ return PreferenceManager.getDefaultSharedPreferences(this);
+ }
+
+ private boolean trackNetwork() {
+ return getPreferences().getBoolean(NETWORK_PREF, true);
+ }
+
+ private boolean trackGPS() {
+ return getPreferences().getBoolean(GPS_PREF, true);
+ }
+
+ private boolean doDebugLogging() {
+ return getPreferences().getBoolean(DEBUG_PREF, true);
+ }
+
+ private boolean trackSignalStrength() {
+ return getPreferences().getBoolean(SIGNAL_PREF, true);
+ }
+
+ private float getLocationMinDistance() {
+ try {
+ String disString = getPreferences().getString(MIN_DIS_PREF, "0");
+ return Float.parseFloat(disString);
+ }
+ catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "Invalid preference for location min distance", e);
+ }
+ return 0;
+ }
+
+ private long getLocationUpdateTime() {
+ try {
+ String timeString = getPreferences().getString(MIN_TIME_PREF, "0");
+ long secondsTime = Long.valueOf(timeString);
+ return secondsTime * 1000;
+ }
+ catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "Invalid preference for location min time", e);
+ }
+ return 0;
+ }
+
+ /**
+ * Shuts down this service
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(LOG_TAG, "Removing location listeners");
+ stopListeners();
+ Toast.makeText(this, "Tracking service stopped", Toast.LENGTH_SHORT);
+ }
+
+ /**
+ * De-registers all location listeners, closes persistent storage
+ */
+ protected synchronized void stopListeners() {
+ LocationManager lm = getLocationManager();
+ if (mListeners != null) {
+ for (LocationTrackingListener listener : mListeners) {
+ lm.removeUpdates(listener);
+ }
+ mListeners.clear();
+ }
+ mListeners = null;
+
+ // stop cell state listener
+ if (mTelephonyManager != null) {
+ mTelephonyManager.listen(mPhoneStateListener, 0);
+ }
+
+ // stop network/wifi listener
+ if (mNetwork != null) {
+ unregisterReceiver(mNetwork);
+ }
+ mNetwork = null;
+
+ mTrackerData = null;
+ if (mPrefListener != null) {
+ getPreferences().unregisterOnSharedPreferenceChangeListener(mPrefListener);
+ mPrefListener = null;
+ }
+ }
+
+ private LocationManager getLocationManager() {
+ return (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+ }
+
+ /**
+ * Determine the current distance from given location to the last
+ * approximated network location
+ *
+ * @param location - new location
+ *
+ * @return float distance in meters
+ */
+ private synchronized float getDistanceFromNetwork(Location location) {
+ float value = 0;
+ if (mNetworkLocation != null) {
+ value = location.distanceTo(mNetworkLocation);
+ }
+ if (LocationManager.NETWORK_PROVIDER.equals(location.getProvider())) {
+ mNetworkLocation = location;
+ }
+ return value;
+ }
+
+ private class LocationTrackingListener implements LocationListener {
+
+ /**
+ * Writes details of location update to tracking file, including
+ * recording the distance between this location update and the last
+ * network location update
+ *
+ * @param location - new location
+ */
+ public void onLocationChanged(Location location) {
+ if (location == null) {
+ return;
+ }
+ float distance = getDistanceFromNetwork(location);
+ mTrackerData.writeEntry(location, distance);
+ }
+
+ /**
+ * Writes update to tracking file
+ *
+ * @param provider - name of disabled provider
+ */
+ public void onProviderDisabled(String provider) {
+ if (doDebugLogging()) {
+ mTrackerData.writeEntry(provider, "provider disabled");
+ }
+ }
+
+ /**
+ * Writes update to tracking file
+ *
+ * @param provider - name of enabled provider
+ */
+ public void onProviderEnabled(String provider) {
+ if (doDebugLogging()) {
+ mTrackerData.writeEntry(provider, "provider enabled");
+ }
+ }
+
+ /**
+ * Writes update to tracking file
+ *
+ * @param provider - name of provider whose status changed
+ * @param status - new status
+ * @param extras - optional set of extra status messages
+ */
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ if (doDebugLogging()) {
+ mTrackerData.writeEntry(provider, "status change: " + status);
+ }
+ }
+ }
+
+ PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onCellLocationChanged(CellLocation location) {
+ try {
+ if (location instanceof GsmCellLocation) {
+ GsmCellLocation cellLocation = (GsmCellLocation)location;
+ String updateMsg = "cid=" + cellLocation.getCid() +
+ ", lac=" + cellLocation.getLac();
+ mTrackerData.writeEntry(CELL_PROVIDER_TAG, updateMsg);
+ } else if (location instanceof CdmaCellLocation) {
+ CdmaCellLocation cellLocation = (CdmaCellLocation)location;
+ String updateMsg = "BID=" + cellLocation.getBaseStationId() +
+ ", SID=" + cellLocation.getSystemId() +
+ ", NID=" + cellLocation.getNetworkId() +
+ ", lat=" + cellLocation.getBaseStationLatitude() +
+ ", long=" + cellLocation.getBaseStationLongitude() +
+ ", SID=" + cellLocation.getSystemId() +
+ ", NID=" + cellLocation.getNetworkId();
+ mTrackerData.writeEntry(CELL_PROVIDER_TAG, updateMsg);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception in CellStateHandler.handleMessage:", e);
+ }
+ }
+
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+ String updateMsg = "cdma dBM=" + signalStrength.getCdmaDbm();
+ mTrackerData.writeEntry(SIGNAL_PROVIDER_TAG, updateMsg);
+ } else if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+ String updateMsg = "gsm signal=" + signalStrength.getGsmSignalStrength();
+ mTrackerData.writeEntry(SIGNAL_PROVIDER_TAG, updateMsg);
+ }
+ }
+ };
+
+ /**
+ * Listener + recorder for mobile or wifi updates
+ */
+ private class NetworkStateBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ WifiManager wifiManager =
+ (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ List<ScanResult> wifiScanResults = wifiManager.getScanResults();
+ String updateMsg = "num scan results=" +
+ (wifiScanResults == null ? "0" : wifiScanResults.size());
+ mTrackerData.writeEntry(WIFI_PROVIDER_TAG, updateMsg);
+
+ } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ String updateMsg;
+ boolean noConnectivity =
+ intent.getBooleanExtra(
+ ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+ if (noConnectivity) {
+ updateMsg = "no connectivity";
+ }
+ else {
+ updateMsg = "connection available";
+ }
+ mTrackerData.writeEntry(DATA_CONN_PROVIDER_TAG, updateMsg);
+
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN);
+
+ String stateString = "unknown";
+ switch (state) {
+ case WifiManager.WIFI_STATE_DISABLED:
+ stateString = "disabled";
+ break;
+ case WifiManager.WIFI_STATE_DISABLING:
+ stateString = "disabling";
+ break;
+ case WifiManager.WIFI_STATE_ENABLED:
+ stateString = "enabled";
+ break;
+ case WifiManager.WIFI_STATE_ENABLING:
+ stateString = "enabling";
+ break;
+ }
+ mTrackerData.writeEntry(WIFI_PROVIDER_TAG,
+ "state = " + stateString);
+ }
+ }
+ }
+
+ private class PreferenceListener implements OnSharedPreferenceChangeListener {
+
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, String key) {
+ Log.d(LOG_TAG, "restarting listeners due to preference change");
+ synchronized (TrackerService.this) {
+ stopListeners();
+ initLocationListeners();
+ }
+ }
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java b/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java
new file mode 100644
index 0000000..22ddf45
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker.data;
+
+import com.android.locationtracker.data.TrackerEntry.EntryType;
+
+/**
+ * Formats tracker data as CSV output
+ */
+class CSVFormatter implements IFormatter {
+
+ private static final String DELIMITER = ", ";
+
+ public String getHeader() {
+ StringBuilder csvBuilder = new StringBuilder();
+ for (String col : TrackerEntry.ATTRIBUTES) {
+ // skip type and id column
+ if (!TrackerEntry.ENTRY_TYPE.equals(col) &&
+ !TrackerEntry.ID_COL.equals(col)) {
+ csvBuilder.append(col);
+ csvBuilder.append(DELIMITER);
+ }
+ }
+ csvBuilder.append("\n");
+ return csvBuilder.toString();
+ }
+
+ public String getOutput(TrackerEntry entry) {
+ StringBuilder rowOutput = new StringBuilder();
+ // these must match order of columns added in getHeader
+ rowOutput.append(entry.getTimestamp());
+ rowOutput.append(DELIMITER);
+ rowOutput.append(entry.getTag());
+ rowOutput.append(DELIMITER);
+ //rowOutput.append(entry.getType());
+ //rowOutput.append(DELIMITER);
+ if (entry.getType() == EntryType.LOCATION_TYPE) {
+ if (entry.getLocation().hasAccuracy()) {
+ rowOutput.append(entry.getLocation().getAccuracy());
+ }
+ rowOutput.append(DELIMITER);
+ rowOutput.append(entry.getLocation().getLatitude());
+ rowOutput.append(DELIMITER);
+ rowOutput.append(entry.getLocation().getLongitude());
+ rowOutput.append(DELIMITER);
+ if (entry.getLocation().hasAltitude()) {
+ rowOutput.append(entry.getLocation().getAltitude());
+ }
+ rowOutput.append(DELIMITER);
+ if (entry.getLocation().hasSpeed()) {
+ rowOutput.append(entry.getLocation().getSpeed());
+ }
+ rowOutput.append(DELIMITER);
+ if (entry.getLocation().hasBearing()) {
+ rowOutput.append(entry.getLocation().getBearing());
+ }
+ rowOutput.append(DELIMITER);
+ rowOutput.append(entry.getDistFromNetLocation());
+ rowOutput.append(DELIMITER);
+ rowOutput.append(DateUtils.getKMLTimestamp(entry.getLocation()
+ .getTime()));
+ rowOutput.append(DELIMITER);
+ }
+ rowOutput.append(entry.getLogMsg());
+ rowOutput.append("\n");
+ return rowOutput.toString();
+ }
+
+ public String getFooter() {
+ // not needed, return empty string
+ return "";
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java b/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java
new file mode 100644
index 0000000..1691f27
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker.data;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Provides formatting date as string utilities
+ */
+public class DateUtils {
+
+ private DateUtils() {
+
+ }
+
+ /**
+ * Returns timestamp given by param in KML format ie yyyy-mm-ddThh:mm:ssZ,
+ * where T is the separator between the date and the time and the time zone
+ * is Z (for UTC)
+ *
+ * @return KML timestamp as String
+ */
+ public static String getKMLTimestamp(long when) {
+ TimeZone tz = TimeZone.getTimeZone("GMT");
+ Calendar c = Calendar.getInstance(tz);
+ c.setTimeInMillis(when);
+ return String.format("%tY-%tm-%tdT%tH:%tM:%tSZ", c, c, c, c, c, c);
+ }
+
+ /**
+ * Helper version of getKMLTimestamp, that returns timestamp for current
+ * time
+ */
+ public static String getCurrentKMLTimestamp() {
+ return getKMLTimestamp(System.currentTimeMillis());
+ }
+
+ /**
+ * Returns timestamp in following format: yyyy-mm-dd-hh-mm-ss
+ */
+ public static String getCurrentTimestamp() {
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(System.currentTimeMillis());
+ return String.format("%tY-%tm-%td-%tH-%tM-%tS", c, c, c, c, c, c);
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java b/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java
new file mode 100644
index 0000000..d413191
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker.data;
+
+/**
+ * interface for formatting tracker data output
+ */
+interface IFormatter {
+
+ String getHeader();
+ String getOutput(TrackerEntry entry);
+ String getFooter();
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java b/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java
new file mode 100644
index 0000000..ef4bbbb
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker.data;
+
+import com.android.locationtracker.data.TrackerEntry.EntryType;
+
+import android.location.Location;
+
+/**
+ * Formats tracker data as KML output
+ */
+class KMLFormatter implements IFormatter {
+
+ public String getHeader() {
+ LineBuilder builder = new LineBuilder();
+ builder.addLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ builder.addLine("<kml xmlns=\"http://earth.google.com/kml/2.2\">");
+ builder.addLine("<Document>");
+ return builder.toString();
+ }
+
+ public String getFooter() {
+ LineBuilder builder = new LineBuilder();
+ builder.addLine("</Document>");
+ builder.addLine("</kml>");
+ return builder.toString();
+ }
+
+ public String getOutput(TrackerEntry entry) {
+ LineBuilder builder = new LineBuilder();
+
+ if (entry.getType() == EntryType.LOCATION_TYPE) {
+
+ Location loc = entry.getLocation();
+ builder.addLine("<Placemark>");
+ builder.addLine("<description>");
+ builder.addLine("accuracy = " + loc.getAccuracy());
+ builder.addLine("distance from last network location = "
+ + entry.getDistFromNetLocation());
+ builder.addLine("</description>");
+ builder.addLine("<TimeStamp>");
+ builder.addLine("<when>" + entry.getTimestamp() + "</when>");
+ builder.addLine("</TimeStamp>");
+ builder.addLine("<Point>");
+ builder.addLine("<coordinates>");
+ builder.addLine(loc.getLongitude() + "," + loc.getLatitude() + ","
+ + loc.getAltitude());
+ builder.addLine("</coordinates>");
+ builder.addLine("</Point>");
+ builder.addLine("</Placemark>");
+ }
+ return builder.toString();
+ }
+
+ private static class LineBuilder {
+ private StringBuilder mBuilder;
+
+ public LineBuilder() {
+ mBuilder = new StringBuilder();
+ }
+
+ public void addLine(String line) {
+ mBuilder.append(line);
+ mBuilder.append("\n");
+ }
+
+ @Override
+ public String toString() {
+ return mBuilder.toString();
+ }
+
+ }
+
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java
new file mode 100644
index 0000000..ad25126
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.location.Location;
+
+/**
+ * Helper class for writing and retrieving data using the TrackerProvider
+ * content provider
+ *
+ */
+public class TrackerDataHelper {
+
+ private Context mContext;
+ /** formats data output */
+ protected IFormatter mFormatter;
+
+ /** formats output as Comma separated value CSV file */
+ public static final IFormatter CSV_FORMATTER = new CSVFormatter();
+ /** formats output as KML file */
+ public static final IFormatter KML_FORMATTER = new KMLFormatter();
+ /** provides no formatting */
+ public static final IFormatter NO_FORMATTER = new IFormatter() {
+ public String getFooter() {
+ return "";
+ }
+
+ public String getHeader() {
+ return "";
+ }
+
+ public String getOutput(TrackerEntry entry) {
+ return "";
+ }
+ };
+
+ /**
+ * Creates instance
+ *
+ * @param context - content context
+ * @param formatter - formats the output from the get*Output* methods
+ */
+ public TrackerDataHelper(Context context, IFormatter formatter) {
+ mContext = context;
+ mFormatter = formatter;
+ }
+
+ /**
+ * Creates a instance with no output formatting capabilities. Useful for
+ * clients that require write-only access
+ */
+ public TrackerDataHelper(Context context) {
+ this(context, NO_FORMATTER);
+ }
+
+ /**
+ * insert given TrackerEntry into content provider
+ */
+ void writeEntry(TrackerEntry entry) {
+ mContext.getContentResolver().insert(TrackerProvider.CONTENT_URI,
+ entry.getAsContentValues());
+ }
+
+ /**
+ * insert given location into tracker data
+ */
+ public void writeEntry(Location loc, float distFromNetLoc) {
+ writeEntry(TrackerEntry.createEntry(loc, distFromNetLoc));
+ }
+
+ /**
+ * insert given log message into tracker data
+ */
+ public void writeEntry(String tag, String logMsg) {
+ writeEntry(TrackerEntry.createEntry(tag, logMsg));
+ }
+
+ /**
+ * Deletes all tracker entries
+ */
+ public void deleteAll() {
+ mContext.getContentResolver().delete(TrackerProvider.CONTENT_URI, null,
+ null);
+ }
+
+ /**
+ * Query tracker data, filtering by given tag
+ *
+ * @param tag
+ * @return Cursor to data
+ */
+ public Cursor query(String tag, int limit) {
+ String selection = (tag == null ? null : TrackerEntry.TAG + "=?");
+ String[] selectionArgs = (tag == null ? null : new String[] {tag});
+ Cursor cursor = mContext.getContentResolver().query(
+ TrackerProvider.CONTENT_URI, TrackerEntry.ATTRIBUTES,
+ selection, selectionArgs, null);
+ if (cursor == null) {
+ return cursor;
+ }
+ int pos = (cursor.getCount() < limit ? 0 : cursor.getCount() - limit);
+ cursor.moveToPosition(pos);
+ return cursor;
+ }
+
+ /**
+ * Retrieves a cursor that starts at the last limit rows
+ *
+ * @param limit
+ * @return a cursor, null if bad things happened
+ */
+ public Cursor query(int limit) {
+ return query(null, limit);
+ }
+
+ /**
+ * Query tracker data, filtering by given tag. mo limit to number of rows
+ * returned
+ *
+ * @param tag
+ * @return Cursor to data
+ */
+ public Cursor query(String tag) {
+ return query(tag, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Returns the output header particular to the associated formatter
+ */
+ public String getOutputHeader() {
+ return mFormatter.getHeader();
+ }
+
+ /**
+ * Returns the output footer particular to the associated formatter
+ */
+ public String getOutputFooter() {
+ return mFormatter.getFooter();
+ }
+
+ /**
+ * Helper method which converts row referenced by given cursor to a string
+ * output
+ *
+ * @param cursor
+ * @return CharSequence output, null if given cursor is invalid or no more
+ * data
+ */
+ public String getNextOutput(Cursor cursor) {
+ if (cursor == null || cursor.isAfterLast()) {
+ return null;
+ }
+ String output = mFormatter.getOutput(TrackerEntry.createEntry(cursor));
+ cursor.moveToNext();
+ return output;
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java
new file mode 100644
index 0000000..8c961d1
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker.data;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.location.Location;
+
+
+/**
+ * Class that holds a tracker entry. An entry can be either a valid location, or
+ * a simple log msg
+ *
+ * It provides a concrete data structure to represent data stored in the
+ * TrackerProvider
+ */
+class TrackerEntry {
+
+ static final String TIMESTAMP = "Timestamp";
+ static final String TAG = "Tag";
+ static final String ENTRY_TYPE = "Type";
+
+ private Location mLocation;
+ private float mDistFromNetLocation;
+ private String mLogMsg;
+
+ static final String ID_COL = "_id";
+ static final String ACCURACY = "Accuracy";
+ static final String LATITUDE = "Latitude";
+ static final String LONGITUDE = "Longitude";
+ static final String ALTITUDE = "Altitude";
+ static final String SPEED = "Speed";
+ static final String BEARING = "Bearing";
+ static final String DIST_NET_LOCATION = "DistFromNetLocation";
+ static final String LOC_TIME = "LocationTime";
+ static final String DEBUG_INFO = "DebugInfo";
+
+ static final String STRING_DATA = "STRING";
+ static final String INT_DATA = "INTEGER";
+ static final String REAL_DATA = "REAL";
+ static final String BLOB_DATA = "BLOB";
+
+ static final String[] ATTRIBUTES = {
+ ID_COL, TIMESTAMP, TAG, ENTRY_TYPE, ACCURACY, LATITUDE, LONGITUDE,
+ ALTITUDE, SPEED, BEARING, DIST_NET_LOCATION, LOC_TIME, DEBUG_INFO};
+ static final String[] ATTRIBUTES_DATA_TYPE = {
+ INT_DATA + " PRIMARY KEY", STRING_DATA, STRING_DATA, STRING_DATA,
+ REAL_DATA, REAL_DATA, REAL_DATA, REAL_DATA, REAL_DATA, REAL_DATA,
+ REAL_DATA, INT_DATA, STRING_DATA};
+
+ // location extra keys used to retrieve debug info
+ private static final String NETWORK_LOCATION_SOURCE_KEY =
+ "networkLocationSource";
+ private static final String NETWORK_LOCATION_TYPE_KEY =
+ "networkLocationType";
+ private static final String[] LOCATION_DEBUG_KEYS = {
+ NETWORK_LOCATION_SOURCE_KEY, NETWORK_LOCATION_TYPE_KEY};
+
+ enum EntryType {
+ LOCATION_TYPE, LOG_TYPE
+ }
+
+ private String mTimestamp;
+ private String mTag;
+ private EntryType mType;
+
+ private TrackerEntry(String tag, EntryType type) {
+ mType = type;
+ mTag = tag;
+ mLocation = null;
+ }
+
+ private TrackerEntry(Location loc) {
+ this(loc.getProvider(), EntryType.LOCATION_TYPE);
+ mLocation = new Location(loc);
+ }
+
+ /**
+ * Creates a TrackerEntry from a Location
+ */
+ static TrackerEntry createEntry(Location loc, float distFromNetLocation) {
+ TrackerEntry entry = new TrackerEntry(loc);
+
+ String timestampVal = DateUtils.getCurrentKMLTimestamp();
+ entry.setTimestamp(timestampVal);
+ entry.setDistFromNetLocation(distFromNetLocation);
+ return entry;
+ }
+
+ /**
+ * Creates a TrackerEntry from a log msg
+ */
+ static TrackerEntry createEntry(String tag, String msg) {
+ TrackerEntry entry = new TrackerEntry(tag, EntryType.LOG_TYPE);
+ String timestampVal = DateUtils.getCurrentKMLTimestamp();
+ entry.setTimestamp(timestampVal);
+ entry.setLogMsg(msg);
+ return entry;
+ }
+
+ private void setTimestamp(String timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ EntryType getType() {
+ return mType;
+ }
+
+ private void setDistFromNetLocation(float distFromNetLocation) {
+ mDistFromNetLocation = distFromNetLocation;
+ }
+
+ private void setLogMsg(String msg) {
+ mLogMsg = msg;
+ }
+
+ private void setLocation(Location location) {
+ mLocation = location;
+ }
+
+ String getTimestamp() {
+ return mTimestamp;
+ }
+
+ String getTag() {
+ return mTag;
+ }
+
+ Location getLocation() {
+ return mLocation;
+ }
+
+ String getLogMsg() {
+ return mLogMsg;
+ }
+
+ float getDistFromNetLocation() {
+ return mDistFromNetLocation;
+ }
+
+ static void buildCreationString(StringBuilder builder) {
+ if (ATTRIBUTES.length != ATTRIBUTES_DATA_TYPE.length) {
+ throw new IllegalArgumentException(
+ "Attribute length does not match data type length");
+ }
+ for (int i = 0; i < ATTRIBUTES_DATA_TYPE.length; i++) {
+ if (i != 0) {
+ builder.append(", ");
+ }
+ builder.append(String.format("%s %s", ATTRIBUTES[i],
+ ATTRIBUTES_DATA_TYPE[i]));
+ }
+ }
+
+ ContentValues getAsContentValues() {
+ ContentValues cValues = new ContentValues(ATTRIBUTES.length);
+ cValues.put(TIMESTAMP, mTimestamp);
+ cValues.put(TAG, mTag);
+ cValues.put(ENTRY_TYPE, mType.toString());
+ if (mType == EntryType.LOCATION_TYPE) {
+ cValues.put(LATITUDE, mLocation.getLatitude());
+ cValues.put(LONGITUDE, mLocation.getLongitude());
+ if (mLocation.hasAccuracy()) {
+ cValues.put(ACCURACY, mLocation.getAccuracy());
+ }
+ if (mLocation.hasAltitude()) {
+ cValues.put(ALTITUDE, mLocation.getAltitude());
+ }
+ if (mLocation.hasSpeed()) {
+ cValues.put(SPEED, mLocation.getSpeed());
+ }
+ if (mLocation.hasBearing()) {
+ cValues.put(BEARING, mLocation.getBearing());
+ }
+ cValues.put(DIST_NET_LOCATION, mDistFromNetLocation);
+ cValues.put(LOC_TIME, mLocation.getTime());
+ StringBuilder debugBuilder = new StringBuilder("");
+ if (mLocation.getExtras() != null) {
+ for (String key : LOCATION_DEBUG_KEYS) {
+ Object val = mLocation.getExtras().get(key);
+ if (val != null) {
+ debugBuilder.append(String.format("%s=%s; ", key, val
+ .toString()));
+ }
+ }
+ }
+ cValues.put(DEBUG_INFO, debugBuilder.toString());
+ } else {
+ cValues.put(DEBUG_INFO, mLogMsg);
+ }
+ return cValues;
+ }
+
+ static TrackerEntry createEntry(Cursor cursor) {
+ String timestamp = cursor.getString(cursor.getColumnIndex(TIMESTAMP));
+ String tag = cursor.getString(cursor.getColumnIndex(TAG));
+ String sType = cursor.getString(cursor.getColumnIndex(ENTRY_TYPE));
+ TrackerEntry entry = new TrackerEntry(tag, EntryType.valueOf(sType));
+ entry.setTimestamp(timestamp);
+ if (entry.getType() == EntryType.LOCATION_TYPE) {
+ Location location = new Location(tag);
+ location.setLatitude(cursor.getFloat(cursor
+ .getColumnIndexOrThrow(LATITUDE)));
+ location.setLongitude(cursor.getFloat(cursor
+ .getColumnIndexOrThrow(LONGITUDE)));
+
+ Float accuracy = getNullableFloat(cursor, ACCURACY);
+ if (accuracy != null) {
+ location.setAccuracy(accuracy);
+ }
+ Float altitude = getNullableFloat(cursor, ALTITUDE);
+ if (altitude != null) {
+ location.setAltitude(altitude);
+ }
+ Float bearing = getNullableFloat(cursor, BEARING);
+ if (bearing != null) {
+ location.setBearing(bearing);
+ }
+ Float speed = getNullableFloat(cursor, SPEED);
+ if (speed != null) {
+ location.setSpeed(speed);
+ }
+ location.setTime(cursor.getLong(cursor.getColumnIndex(LOC_TIME)));
+ entry.setLocation(location);
+ }
+ entry.setLogMsg(cursor.getString(cursor.getColumnIndex(DEBUG_INFO)));
+
+ return entry;
+ }
+
+ private static Float getNullableFloat(Cursor cursor, String colName) {
+ Float retValue = null;
+ int colIndex = cursor.getColumnIndexOrThrow(colName);
+ if (!cursor.isNull(colIndex)) {
+ retValue = cursor.getFloat(colIndex);
+ }
+ return retValue;
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
new file mode 100644
index 0000000..0247ef0
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker.data;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.database.Cursor;
+import android.view.View;
+import android.widget.ResourceCursorAdapter;
+import android.widget.TextView;
+
+import com.android.locationtracker.R;
+
+/**
+ * Used to bind Tracker data to a list view UI
+ */
+public class TrackerListHelper extends TrackerDataHelper {
+
+ private ListActivity mActivity;
+
+ // sort entries by most recent first
+ private static final String SORT_ORDER = TrackerEntry.ID_COL + " DESC";
+
+ public TrackerListHelper(ListActivity activity) {
+ super(activity, TrackerDataHelper.CSV_FORMATTER);
+ mActivity = activity;
+ }
+
+ /**
+ * Helper method for binding the list activities UI to the tracker data
+ * Tracker data will be sorted in most-recent first order
+ * Will enable automatic UI changes as tracker data changes
+ *
+ * @param layout - layout to populate data
+ */
+ public void bindListUI(int layout) {
+ Cursor cursor = mActivity.managedQuery(TrackerProvider.CONTENT_URI,
+ TrackerEntry.ATTRIBUTES, null, null, SORT_ORDER);
+ // Used to map tracker entries from the database to views
+ TrackerAdapter adapter = new TrackerAdapter(mActivity, layout, cursor);
+ mActivity.setListAdapter(adapter);
+ cursor.setNotificationUri(mActivity.getContentResolver(),
+ TrackerProvider.CONTENT_URI);
+
+ }
+
+ private class TrackerAdapter extends ResourceCursorAdapter {
+
+ public TrackerAdapter(Context context, int layout, Cursor c) {
+ super(context, layout, c);
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ final TextView v = (TextView) view
+ .findViewById(R.id.entrylist_item);
+ String rowText = mFormatter.getOutput(TrackerEntry
+ .createEntry(cursor));
+ v.setText(rowText);
+ }
+ }
+}
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java
new file mode 100644
index 0000000..ea849e0
--- /dev/null
+++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.locationtracker.data;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.util.Log;
+
+/**
+ * Content provider for location tracking.
+ *
+ * It is recommended to use the TrackerDataHelper class to access this data
+ * rather than this class directly
+ */
+public class TrackerProvider extends ContentProvider {
+
+ public static final Uri CONTENT_URI = Uri
+ .parse("content://com.android.locationtracker");
+
+ private static final String DB_NAME = "tracking.db";
+ private static final String TABLE_NAME = "tracking";
+ private static final int DB_VERSION = 1;
+
+ private static final String LOG_TAG = "TrackerProvider";
+
+ /**
+ * This class helps open, create, and upgrade the database file.
+ */
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+
+ DatabaseHelper(Context context) {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ StringBuilder queryBuilder = new StringBuilder();
+ queryBuilder.append(String.format("CREATE TABLE %s (", TABLE_NAME));
+ TrackerEntry.buildCreationString(queryBuilder);
+
+ queryBuilder.append(");");
+ db.execSQL(queryBuilder.toString());
+ db.setVersion(DB_VERSION);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO: reimplement this when dB version changes
+ Log.w(LOG_TAG, "Upgrading database from version " + oldVersion
+ + " to " + newVersion
+ + ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ onCreate(db);
+ }
+ }
+
+ private DatabaseHelper mOpenHelper;
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new DatabaseHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int result = db.delete(TABLE_NAME, selection, selectionArgs);
+ getContext().getContentResolver().notifyChange(uri, null);
+ return result;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ long rowId = db.insert(TABLE_NAME, null, values);
+ if (rowId > 0) {
+ Uri addedUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
+ getContext().getContentResolver().notifyChange(addedUri, null);
+ return addedUri;
+ }
+ return null;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ // TODO: extract limit from URI ?
+ Cursor cursor = db.query(TABLE_NAME, projection, selection,
+ selectionArgs, null, null, sortOrder);
+ getContext().getContentResolver().notifyChange(uri, null);
+ return cursor;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+}