Merge change 6608 into donut
* changes:
Move the watchdog timer to a separate thread...
diff --git a/api/current.xml b/api/current.xml
index 78562f0..a396c4d 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -496,6 +496,17 @@
visibility="public"
>
</field>
+<field name="GLOBAL_SEARCH"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.permission.GLOBAL_SEARCH""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="HARDWARE_TEST"
type="java.lang.String"
transient="false"
@@ -2275,6 +2286,17 @@
visibility="public"
>
</field>
+<field name="autoUrlDetect"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843404"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="background"
type="int"
transient="false"
@@ -3386,17 +3408,6 @@
visibility="public"
>
</field>
-<field name="donut_resource_pad20"
- type="int"
- transient="false"
- volatile="false"
- value="16843404"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="donut_resource_pad3"
type="int"
transient="false"
@@ -25945,6 +25956,17 @@
visibility="public"
>
</method>
+<method name="getPathPermissions"
+ return="android.content.pm.PathPermission[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="getReadPermission"
return="java.lang.String"
abstract="false"
@@ -26113,6 +26135,19 @@
<parameter name="sortOrder" type="java.lang.String">
</parameter>
</method>
+<method name="setPathPermissions"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="permissions" type="android.content.pm.PathPermission[]">
+</parameter>
+</method>
<method name="setReadPermission"
return="void"
abstract="false"
@@ -37714,6 +37749,73 @@
>
</field>
</class>
+<class name="PathPermission"
+ extends="android.os.PatternMatcher"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="PathPermission"
+ type="android.content.pm.PathPermission"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pattern" type="java.lang.String">
+</parameter>
+<parameter name="type" type="int">
+</parameter>
+<parameter name="readPermission" type="java.lang.String">
+</parameter>
+<parameter name="writePermission" type="java.lang.String">
+</parameter>
+</constructor>
+<constructor name="PathPermission"
+ type="android.content.pm.PathPermission"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="src" type="android.os.Parcel">
+</parameter>
+</constructor>
+<method name="getReadPermission"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getWritePermission"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="PermissionGroupInfo"
extends="android.content.pm.PackageItemInfo"
abstract="false"
@@ -38043,6 +38145,17 @@
visibility="public"
>
</field>
+<field name="pathPermissions"
+ type="android.content.pm.PathPermission[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="readPermission"
type="java.lang.String"
transient="false"
diff --git a/awt/Android.mk b/awt/Android.mk
index c7480f53a..213c6ce 100644
--- a/awt/Android.mk
+++ b/awt/Android.mk
@@ -28,4 +28,4 @@
LOCAL_DX_FLAGS := --core-library
-include $(BUILD_JAVA_LIBRARY)
+#include $(BUILD_JAVA_LIBRARY)
diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp
index 022fe5a..e4b6791 100644
--- a/camera/libcameraservice/CameraService.cpp
+++ b/camera/libcameraservice/CameraService.cpp
@@ -504,8 +504,7 @@
}
// start recording mode
- ret = mHardware->startRecording(recordingCallback,
- mCameraService.get());
+ ret = mHardware->startRecording(recordingCallback, mCameraService.get());
if (ret != NO_ERROR) {
LOGE("mHardware->startRecording() failed with status %d", ret);
}
@@ -798,7 +797,7 @@
}
// recording callback
-void CameraService::Client::recordingCallback(const sp<IMemory>& mem, void* user)
+void CameraService::Client::recordingCallback(nsecs_t timestamp, const sp<IMemory>& mem, void* user)
{
LOGV("recordingCallback");
sp<Client> client = getClientFromCookie(user);
@@ -806,7 +805,7 @@
return;
}
// The strong pointer guarantees the client will exist, but no lock is held.
- client->postRecordingFrame(mem);
+ client->postRecordingFrame(timestamp, mem);
}
// take a picture - image is returned in callback
@@ -1072,14 +1071,14 @@
mCameraClient->dataCallback(CAMERA_MSG_PREVIEW_FRAME, frame);
}
-void CameraService::Client::postRecordingFrame(const sp<IMemory>& frame)
+void CameraService::Client::postRecordingFrame(nsecs_t timestamp, const sp<IMemory>& frame)
{
LOGV("postRecordingFrame");
if (frame == 0) {
LOGW("frame is a null pointer");
return;
}
- mCameraClient->dataCallback(CAMERA_MSG_VIDEO_FRAME, frame);
+ mCameraClient->dataCallbackTimestamp(timestamp, CAMERA_MSG_VIDEO_FRAME, frame);
}
void CameraService::Client::postPreviewFrame(const sp<IMemory>& mem)
diff --git a/camera/libcameraservice/CameraService.h b/camera/libcameraservice/CameraService.h
index 0f07673..ea93789 100644
--- a/camera/libcameraservice/CameraService.h
+++ b/camera/libcameraservice/CameraService.h
@@ -132,7 +132,7 @@
status_t checkPid();
- static void recordingCallback(const sp<IMemory>& mem, void* user);
+ static void recordingCallback(nsecs_t timestamp, const sp<IMemory>& mem, void* user);
static void previewCallback(const sp<IMemory>& mem, void* user);
static void shutterCallback(void *user);
static void yuvPictureCallback(const sp<IMemory>& mem, void* user);
@@ -144,7 +144,7 @@
void postRaw(const sp<IMemory>& mem);
void postJpeg(const sp<IMemory>& mem);
void postPreviewFrame(const sp<IMemory>& mem);
- void postRecordingFrame(const sp<IMemory>& frame);
+ void postRecordingFrame(nsecs_t timestamp, const sp<IMemory>& frame);
void copyFrameAndPostCopiedFrame(sp<IMemoryHeap> heap, size_t offset, size_t size);
void postError(status_t error);
void postAutoFocus(bool focused);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ca9632a..6c2560d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -606,7 +606,6 @@
private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
- private static final String SAVED_SEARCH_DIALOG_KEY = "android:search_dialog";
private SparseArray<Dialog> mManagedDialogs;
@@ -630,7 +629,6 @@
/*package*/ int mConfigChangeFlags;
/*package*/ Configuration mCurrentConfig;
private SearchManager mSearchManager;
- private Bundle mSearchDialogState = null;
private Window mWindow;
@@ -808,13 +806,6 @@
final void performRestoreInstanceState(Bundle savedInstanceState) {
onRestoreInstanceState(savedInstanceState);
restoreManagedDialogs(savedInstanceState);
-
- // Also restore the state of a search dialog (if any)
- // TODO more generic than just this manager
- Bundle searchState = savedInstanceState.getBundle(SAVED_SEARCH_DIALOG_KEY);
- if (searchState != null) {
- mSearchManager.restoreSearchDialog(searchState);
- }
}
/**
@@ -866,7 +857,7 @@
if (dialogState != null) {
// Calling onRestoreInstanceState() below will invoke dispatchOnCreate
// so tell createDialog() not to do it, otherwise we get an exception
- final Dialog dialog = createDialog(dialogId, false);
+ final Dialog dialog = createDialog(dialogId, dialogState);
mManagedDialogs.put(dialogId, dialog);
onPrepareDialog(dialogId, dialog);
dialog.onRestoreInstanceState(dialogState);
@@ -874,13 +865,13 @@
}
}
- private Dialog createDialog(Integer dialogId, boolean dispatchOnCreate) {
+ private Dialog createDialog(Integer dialogId, Bundle state) {
final Dialog dialog = onCreateDialog(dialogId);
if (dialog == null) {
throw new IllegalArgumentException("Activity#onCreateDialog did "
+ "not create a dialog for id " + dialogId);
}
- if (dispatchOnCreate) dialog.dispatchOnCreate(null);
+ dialog.dispatchOnCreate(state);
return dialog;
}
@@ -1030,14 +1021,6 @@
final void performSaveInstanceState(Bundle outState) {
onSaveInstanceState(outState);
saveManagedDialogs(outState);
-
- // Also save the state of a search dialog (if any)
- // TODO more generic than just this manager
- // onPause() should always be called before this method, so mSearchManagerState
- // should be up to date.
- if (mSearchDialogState != null) {
- outState.putBundle(SAVED_SEARCH_DIALOG_KEY, mSearchDialogState);
- }
}
/**
@@ -1317,10 +1300,6 @@
c.mCursor.close();
}
}
-
- // Clear any search state saved in performPause(). If the state may be needed in the
- // future, it will have been saved by performSaveInstanceState()
- mSearchDialogState = null;
}
/**
@@ -1341,11 +1320,7 @@
*/
public void onConfigurationChanged(Configuration newConfig) {
mCalled = true;
-
- // also update search dialog if showing
- // TODO more generic than just this manager
- mSearchManager.onConfigurationChanged(newConfig);
-
+
if (mWindow != null) {
// Pass the configuration changed event to the window
mWindow.onConfigurationChanged(newConfig);
@@ -2432,7 +2407,7 @@
}
Dialog dialog = mManagedDialogs.get(id);
if (dialog == null) {
- dialog = createDialog(id, true);
+ dialog = createDialog(id, null);
mManagedDialogs.put(id, dialog);
}
@@ -3575,20 +3550,12 @@
"Activity " + mComponent.toShortString() +
" did not call through to super.onPostResume()");
}
-
- // restore search dialog, if any
- if (mSearchDialogState != null) {
- mSearchManager.restoreSearchDialog(mSearchDialogState);
- }
- mSearchDialogState = null;
}
final void performPause() {
onPause();
- // save search dialog state if the search dialog is open,
- // and then dismiss the search dialog
- mSearchDialogState = mSearchManager.saveSearchDialog();
+ // dismiss the search dialog if it is open
mSearchManager.stopSearch();
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 222fe75..2b165fc 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -292,8 +292,10 @@
// internal method to make sure mcreated is set properly without requiring
// users to call through to super in onCreate
void dispatchOnCreate(Bundle savedInstanceState) {
- onCreate(savedInstanceState);
- mCreated = true;
+ if (!mCreated) {
+ onCreate(savedInstanceState);
+ mCreated = true;
+ }
}
/**
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index e8bd60a..5b62192 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -36,8 +36,4 @@
boolean globalSearch,
ISearchManagerCallback searchManagerCallback);
void stopSearch();
- boolean isVisible();
- Bundle onSaveInstanceState();
- void onRestoreInstanceState(in Bundle savedInstanceState);
- void onConfigurationChanged(in Configuration newConfig);
}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index fdb619a..6d6aca4 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -19,13 +19,11 @@
import static android.app.SuggestionsAdapter.getColumnString;
import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -33,8 +31,8 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
@@ -95,11 +93,7 @@
private static final int SEARCH_PLATE_LEFT_PADDING_GLOBAL = 12;
private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7;
-
- // interaction with runtime
- private IntentFilter mCloseDialogsFilter;
- private IntentFilter mPackageFilter;
-
+
// views & widgets
private TextView mBadgeLabel;
private ImageView mAppIcon;
@@ -210,15 +204,7 @@
// Touching outside of the search dialog will dismiss it
setCanceledOnTouchOutside(true);
-
- // Set up broadcast filters
- mCloseDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mPackageFilter = new IntentFilter();
- mPackageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- mPackageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- mPackageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- mPackageFilter.addDataScheme("package");
-
+
// Save voice intent for later queries/launching
mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -382,15 +368,6 @@
return true;
}
-
- @Override
- protected void onStart() {
- super.onStart();
-
- // receive broadcasts
- getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter);
- getContext().registerReceiver(mBroadcastReceiver, mPackageFilter);
- }
/**
* The search dialog is being dismissed, so handle all of the local shutdown operations.
@@ -401,14 +378,7 @@
@Override
public void onStop() {
super.onStop();
-
- // stop receiving broadcasts (throws exception if none registered)
- try {
- getContext().unregisterReceiver(mBroadcastReceiver);
- } catch (RuntimeException e) {
- // This is OK - it just means we didn't have any registered
- }
-
+
closeSuggestionsAdapter();
// dump extra memory we're hanging on to
@@ -455,12 +425,15 @@
/**
* Save the minimal set of data necessary to recreate the search
*
- * @return A bundle with the state of the dialog.
+ * @return A bundle with the state of the dialog, or {@code null} if the search
+ * dialog is not showing.
*/
@Override
public Bundle onSaveInstanceState() {
+ if (!isShowing()) return null;
+
Bundle bundle = new Bundle();
-
+
// setup info so I can recreate this particular search
bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
@@ -483,6 +456,8 @@
*/
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
+ if (savedInstanceState == null) return;
+
ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH);
@@ -509,7 +484,7 @@
/**
* Called after resources have changed, e.g. after screen rotation or locale change.
*/
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigurationChanged() {
if (isShowing()) {
// Redraw (resources may have changed)
updateSearchButton();
@@ -777,7 +752,7 @@
}
public void afterTextChanged(Editable s) {
- if (!mSearchAutoComplete.isPerformingCompletion()) {
+ if (mSearchable.autoUrlDetect() && !mSearchAutoComplete.isPerformingCompletion()) {
// The user changed the query, check if it is a URL and if so change the search
// button in the soft keyboard to the 'Go' button.
int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION));
@@ -987,17 +962,19 @@
&& event.getAction() == KeyEvent.ACTION_UP) {
v.cancelLongPress();
- // If this is a url entered by the user and we displayed the 'Go' button which
- // the user clicked, launch the url instead of using it as a search query.
- if ((mSearchAutoCompleteImeOptions & EditorInfo.IME_MASK_ACTION)
- == EditorInfo.IME_ACTION_GO) {
- Uri uri = Uri.parse(fixUrl(mSearchAutoComplete.getText().toString()));
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- launchIntent(intent);
- } else {
- // Launch as a regular search.
- launchQuerySearch();
+ if (mSearchable.autoUrlDetect()) {
+ // If this is a url entered by the user & we displayed the 'Go' button which
+ // the user clicked, launch the url instead of using it as a search query.
+ if ((mSearchAutoCompleteImeOptions & EditorInfo.IME_MASK_ACTION)
+ == EditorInfo.IME_ACTION_GO) {
+ Uri uri = Uri.parse(fixUrl(mSearchAutoComplete.getText().toString()));
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ launchIntent(intent);
+ } else {
+ // Launch as a regular search.
+ launchQuerySearch();
+ }
}
return true;
}
@@ -1012,35 +989,11 @@
return false;
}
};
-
- /**
- * When the ACTION_CLOSE_SYSTEM_DIALOGS intent is received, we should close ourselves
- * immediately, in order to allow a higher-priority UI to take over
- * (e.g. phone call received).
- *
- * When a package is added, removed or changed, our current context
- * may no longer be valid. This would only happen if a package is installed/removed exactly
- * when the search bar is open. So for now we're just going to close the search
- * bar.
- * Anything fancier would require some checks to see if the user's context was still valid.
- * Which would be messier.
- */
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
- cancel();
- } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)
- || Intent.ACTION_PACKAGE_REMOVED.equals(action)
- || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
- cancel();
- }
- }
- };
@Override
- public void cancel() {
+ public void dismiss() {
+ if (!isShowing()) return;
+
// We made sure the IME was displayed, so also make sure it is closed
// when we go away.
InputMethodManager imm = (InputMethodManager)getContext()
@@ -1050,7 +1003,7 @@
getWindow().getDecorView().getWindowToken(), 0);
}
- super.cancel();
+ super.dismiss();
}
/**
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index e5ba6a4..5d25f10 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -20,7 +20,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.res.Configuration;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@@ -1730,59 +1729,6 @@
}
/**
- * Saves the state of the search UI.
- *
- * @return A Bundle containing the state of the search dialog, or {@code null}
- * if the search UI is not visible.
- *
- * @hide
- */
- public Bundle saveSearchDialog() {
- if (DBG) debug("saveSearchDialog(), mIsShowing=" + mIsShowing);
- if (!mIsShowing) return null;
- try {
- return mService.onSaveInstanceState();
- } catch (RemoteException ex) {
- Log.e(TAG, "onSaveInstanceState() failed: " + ex);
- return null;
- }
- }
-
- /**
- * Restores the state of the search dialog.
- *
- * @param searchDialogState Bundle to read the state from.
- *
- * @hide
- */
- public void restoreSearchDialog(Bundle searchDialogState) {
- if (DBG) debug("restoreSearchDialog(" + searchDialogState + ")");
- if (searchDialogState == null) return;
- try {
- mService.onRestoreInstanceState(searchDialogState);
- } catch (RemoteException ex) {
- Log.e(TAG, "onRestoreInstanceState() failed: " + ex);
- }
- }
-
- /**
- * Update the search dialog after a configuration change.
- *
- * @param newConfig The new configuration.
- *
- * @hide
- */
- public void onConfigurationChanged(Configuration newConfig) {
- if (DBG) debug("onConfigurationChanged(" + newConfig + "), mIsShowing=" + mIsShowing);
- if (!mIsShowing) return;
- try {
- mService.onConfigurationChanged(newConfig);
- } catch (RemoteException ex) {
- Log.e(TAG, "onConfigurationChanged() failed:" + ex);
- }
- }
-
- /**
* Gets information about a searchable activity. This method is static so that it can
* be used from non-Activity contexts.
*
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 5cc5730..6b50405 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -17,6 +17,7 @@
package android.content;
import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
@@ -29,6 +30,7 @@
import android.net.Uri;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import java.io.File;
import java.io.FileNotFoundException;
@@ -65,8 +67,10 @@
*/
public abstract class ContentProvider implements ComponentCallbacks {
private Context mContext = null;
+ private int mMyUid;
private String mReadPermission;
private String mWritePermission;
+ private PathPermission[] mPathPermissions;
private Transport mTransport = new Transport();
@@ -108,24 +112,20 @@
public IBulkCursor bulkQuery(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
IContentObserver observer, CursorWindow window) {
- checkReadPermission(uri);
+ enforceReadPermission(uri);
Cursor cursor = ContentProvider.this.query(uri, projection,
selection, selectionArgs, sortOrder);
if (cursor == null) {
return null;
}
- String wperm = getWritePermission();
return new CursorToBulkCursorAdaptor(cursor, observer,
ContentProvider.this.getClass().getName(),
- wperm == null ||
- getContext().checkCallingOrSelfPermission(getWritePermission())
- == PackageManager.PERMISSION_GRANTED,
- window);
+ hasWritePermission(uri), window);
}
public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
- checkReadPermission(uri);
+ enforceReadPermission(uri);
return ContentProvider.this.query(uri, projection, selection,
selectionArgs, sortOrder);
}
@@ -136,55 +136,84 @@
public Uri insert(Uri uri, ContentValues initialValues) {
- checkWritePermission(uri);
+ enforceWritePermission(uri);
return ContentProvider.this.insert(uri, initialValues);
}
public int bulkInsert(Uri uri, ContentValues[] initialValues) {
- checkWritePermission(uri);
+ enforceWritePermission(uri);
return ContentProvider.this.bulkInsert(uri, initialValues);
}
public int delete(Uri uri, String selection, String[] selectionArgs) {
- checkWritePermission(uri);
+ enforceWritePermission(uri);
return ContentProvider.this.delete(uri, selection, selectionArgs);
}
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
- checkWritePermission(uri);
+ enforceWritePermission(uri);
return ContentProvider.this.update(uri, values, selection, selectionArgs);
}
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
- if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
- else checkReadPermission(uri);
+ if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
+ else enforceReadPermission(uri);
return ContentProvider.this.openFile(uri, mode);
}
public AssetFileDescriptor openAssetFile(Uri uri, String mode)
throws FileNotFoundException {
- if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
- else checkReadPermission(uri);
+ if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
+ else enforceReadPermission(uri);
return ContentProvider.this.openAssetFile(uri, mode);
}
public ISyncAdapter getSyncAdapter() {
- checkWritePermission(null);
+ enforceWritePermission(null);
SyncAdapter sa = ContentProvider.this.getSyncAdapter();
return sa != null ? sa.getISyncAdapter() : null;
}
- private void checkReadPermission(Uri uri) {
+ private void enforceReadPermission(Uri uri) {
+ final int uid = Binder.getCallingUid();
+ if (uid == mMyUid) {
+ return;
+ }
+
+ final Context context = getContext();
final String rperm = getReadPermission();
final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- if (getContext().checkUriPermission(uri, rperm, null, pid, uid,
+ if (rperm == null
+ || context.checkPermission(rperm, pid, uid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ PathPermission[] pps = getPathPermissions();
+ if (pps != null) {
+ final String path = uri.getPath();
+ int i = pps.length;
+ while (i > 0) {
+ i--;
+ final PathPermission pp = pps[i];
+ final String pprperm = pp.getReadPermission();
+ if (pprperm != null && pp.match(path)) {
+ if (context.checkPermission(pprperm, pid, uid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+ }
+ }
+
+ if (context.checkUriPermission(uri, pid, uid,
Intent.FLAG_GRANT_READ_URI_PERMISSION)
== PackageManager.PERMISSION_GRANTED) {
return;
}
+
String msg = "Permission Denial: reading "
+ ContentProvider.this.getClass().getName()
+ " uri " + uri + " from pid=" + Binder.getCallingPid()
@@ -193,20 +222,57 @@
throw new SecurityException(msg);
}
- private void checkWritePermission(Uri uri) {
+ private boolean hasWritePermission(Uri uri) {
+ final int uid = Binder.getCallingUid();
+ if (uid == mMyUid) {
+ return true;
+ }
+
+ final Context context = getContext();
final String wperm = getWritePermission();
final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- if (getContext().checkUriPermission(uri, null, wperm, pid, uid,
+ if (wperm == null
+ || context.checkPermission(wperm, pid, uid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+
+ PathPermission[] pps = getPathPermissions();
+ if (pps != null) {
+ final String path = uri.getPath();
+ int i = pps.length;
+ while (i > 0) {
+ i--;
+ final PathPermission pp = pps[i];
+ final String ppwperm = pp.getWritePermission();
+ if (ppwperm != null && pp.match(path)) {
+ if (context.checkPermission(ppwperm, pid, uid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ }
+ }
+
+ if (context.checkUriPermission(uri, pid, uid,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
== PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private void enforceWritePermission(Uri uri) {
+ if (hasWritePermission(uri)) {
return;
}
+
String msg = "Permission Denial: writing "
+ ContentProvider.this.getClass().getName()
+ " uri " + uri + " from pid=" + Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
- + " requires " + wperm;
+ + " requires " + getWritePermission();
throw new SecurityException(msg);
}
}
@@ -266,6 +332,28 @@
}
/**
+ * Change the path-based permission required to read and/or write data in
+ * the content provider. This is normally set for you from its manifest
+ * information when the provider is first created.
+ *
+ * @param permissions Array of path permission descriptions.
+ */
+ protected final void setPathPermissions(PathPermission[] permissions) {
+ mPathPermissions = permissions;
+ }
+
+ /**
+ * Return the path-based permissions required for read and/or write access to
+ * this content provider. This method can be called from multiple
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
+ */
+ public final PathPermission[] getPathPermissions() {
+ return mPathPermissions;
+ }
+
+ /**
* Called when the provider is being started.
*
* @return true if the provider was successfully loaded, false otherwise
@@ -600,9 +688,11 @@
*/
if (mContext == null) {
mContext = context;
+ mMyUid = Process.myUid();
if (info != null) {
setReadPermission(info.readPermission);
setWritePermission(info.writePermission);
+ setPathPermissions(info.pathPermissions);
}
ContentProvider.this.onCreate();
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index bcf95b6..0d00f21 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -173,7 +173,7 @@
* {@hide}
*/
public static final int ANY_DENSITY = -1;
- private static final int[] ANY_DENSITIES_ARRAY = { ANY_DENSITY };
+ static final int[] ANY_DENSITIES_ARRAY = { ANY_DENSITY };
/**
* Flags associated with the application. Any combination of
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 558b0c3..0e2deed 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -945,15 +945,25 @@
>= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS;
}
-
+ int densities[] = null;
int size = pkg.supportsDensityList.size();
if (size > 0) {
- int densities[] = pkg.supportsDensities = new int[size];
+ densities = pkg.supportsDensities = new int[size];
List<Integer> densityList = pkg.supportsDensityList;
for (int i = 0; i < size; i++) {
densities[i] = densityList.get(i);
}
}
+ /**
+ * TODO: enable this before code freeze. b/1967935
+ * *
+ if ((densities == null || densities.length == 0)
+ && (pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) {
+ pkg.supportsDensities = ApplicationInfo.ANY_DENSITIES_ARRAY;
+ }
+ */
+
return pkg;
}
@@ -1908,6 +1918,7 @@
outInfo.metaData, outError)) == null) {
return false;
}
+
} else if (parser.getName().equals("grant-uri-permission")) {
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestGrantUriPermission);
@@ -1931,7 +1942,7 @@
if (str != null) {
pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
}
-
+
sa.recycle();
if (pa != null) {
@@ -1946,6 +1957,101 @@
outInfo.info.uriPermissionPatterns = newp;
}
outInfo.info.grantUriPermissions = true;
+ } else {
+ if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>");
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>";
+ return false;
+ }
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (parser.getName().equals("path-permission")) {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestPathPermission);
+
+ PathPermission pa = null;
+
+ String permission = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_permission);
+ String readPermission = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_readPermission);
+ if (readPermission == null) {
+ readPermission = permission;
+ }
+ String writePermission = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_writePermission);
+ if (writePermission == null) {
+ writePermission = permission;
+ }
+
+ boolean havePerm = false;
+ if (readPermission != null) {
+ readPermission = readPermission.intern();
+ havePerm = true;
+ }
+ if (writePermission != null) {
+ writePermission = readPermission.intern();
+ havePerm = true;
+ }
+
+ if (!havePerm) {
+ if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "No readPermission or writePermssion for <path-permission>");
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ outError[0] = "No readPermission or writePermssion for <path-permission>";
+ return false;
+ }
+
+ String path = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_path);
+ if (path != null) {
+ pa = new PathPermission(path,
+ PatternMatcher.PATTERN_LITERAL, readPermission, writePermission);
+ }
+
+ path = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_pathPrefix);
+ if (path != null) {
+ pa = new PathPermission(path,
+ PatternMatcher.PATTERN_PREFIX, readPermission, writePermission);
+ }
+
+ path = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_pathPattern);
+ if (path != null) {
+ pa = new PathPermission(path,
+ PatternMatcher.PATTERN_SIMPLE_GLOB, readPermission, writePermission);
+ }
+
+ sa.recycle();
+
+ if (pa != null) {
+ if (outInfo.info.pathPermissions == null) {
+ outInfo.info.pathPermissions = new PathPermission[1];
+ outInfo.info.pathPermissions[0] = pa;
+ } else {
+ final int N = outInfo.info.pathPermissions.length;
+ PathPermission[] newp = new PathPermission[N+1];
+ System.arraycopy(outInfo.info.pathPermissions, 0, newp, 0, N);
+ newp[N] = pa;
+ outInfo.info.pathPermissions = newp;
+ }
+ } else {
+ if (!RIGID_PARSER) {
+ Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+ Log.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>");
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>";
+ return false;
}
XmlUtils.skipCurrentTag(parser);
diff --git a/core/java/android/content/pm/PathPermission.java b/core/java/android/content/pm/PathPermission.java
new file mode 100644
index 0000000..7e49d7d
--- /dev/null
+++ b/core/java/android/content/pm/PathPermission.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+
+/**
+ * Description of permissions needed to access a particular path
+ * in a {@link ProviderInfo}.
+ */
+public class PathPermission extends PatternMatcher {
+ private final String mReadPermission;
+ private final String mWritePermission;
+
+ public PathPermission(String pattern, int type, String readPermission,
+ String writePermission) {
+ super(pattern, type);
+ mReadPermission = readPermission;
+ mWritePermission = writePermission;
+ }
+
+ public String getReadPermission() {
+ return mReadPermission;
+ }
+
+ public String getWritePermission() {
+ return mWritePermission;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mReadPermission);
+ dest.writeString(mWritePermission);
+ }
+
+ public PathPermission(Parcel src) {
+ super(src);
+ mReadPermission = src.readString();
+ mWritePermission = src.readString();
+ }
+
+ public static final Parcelable.Creator<PathPermission> CREATOR
+ = new Parcelable.Creator<PathPermission>() {
+ public PathPermission createFromParcel(Parcel source) {
+ return new PathPermission(source);
+ }
+
+ public PathPermission[] newArray(int size) {
+ return new PathPermission[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index b67ddf6..d01460e 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -28,6 +28,7 @@
*/
public final class ProviderInfo extends ComponentInfo
implements Parcelable {
+
/** The name provider is published under content:// */
public String authority = null;
@@ -56,6 +57,14 @@
*/
public PatternMatcher[] uriPermissionPatterns = null;
+ /**
+ * If non-null, these are path-specific permissions that are allowed for
+ * accessing the provider. Any permissions listed here will allow a
+ * holding client to access the provider, and the provider will check
+ * the URI it provides when making calls against the patterns here.
+ */
+ public PathPermission[] pathPermissions = null;
+
/** If true, this content provider allows multiple instances of itself
* to run in different process. If false, a single instances is always
* run in {@link #processName}. */
@@ -78,6 +87,7 @@
writePermission = orig.writePermission;
grantUriPermissions = orig.grantUriPermissions;
uriPermissionPatterns = orig.uriPermissionPatterns;
+ pathPermissions = orig.pathPermissions;
multiprocess = orig.multiprocess;
initOrder = orig.initOrder;
isSyncable = orig.isSyncable;
@@ -94,6 +104,7 @@
out.writeString(writePermission);
out.writeInt(grantUriPermissions ? 1 : 0);
out.writeTypedArray(uriPermissionPatterns, parcelableFlags);
+ out.writeTypedArray(pathPermissions, parcelableFlags);
out.writeInt(multiprocess ? 1 : 0);
out.writeInt(initOrder);
out.writeInt(isSyncable ? 1 : 0);
@@ -122,6 +133,7 @@
writePermission = in.readString();
grantUriPermissions = in.readInt() != 0;
uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
+ pathPermissions = in.createTypedArray(PathPermission.CREATOR);
multiprocess = in.readInt() != 0;
initOrder = in.readInt();
isSyncable = in.readInt() != 0;
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index dfe304d..ebe556e 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -51,18 +51,6 @@
public static final int DEFAULT_PORTRAIT_HEIGHT = 480;
/**
- * The x-shift mode that controls the position of the content or the window under
- * compatibility mode.
- * {@see getTranslator}
- * {@see Translator#mShiftMode}
- */
- private static final int X_SHIFT_NONE = 0;
- private static final int X_SHIFT_CONTENT = 1;
- private static final int X_SHIFT_AND_CLIP_CONTENT = 2;
- private static final int X_SHIFT_WINDOW = 3;
-
-
- /**
* A compatibility flags
*/
private int mCompatibilityFlags;
@@ -106,20 +94,6 @@
*/
public final int appFlags;
- /**
- * Window size in Compatibility Mode, in real pixels. This is updated by
- * {@link DisplayMetrics#updateMetrics}.
- */
- private int mWidth;
- private int mHeight;
-
- /**
- * The x offset to center the window content. In X_SHIFT_WINDOW mode, the offset is added
- * to the window's layout. In X_SHIFT_CONTENT/X_SHIFT_AND_CLIP_CONTENT mode, the offset
- * is used to translate the Canvas.
- */
- private int mXOffset;
-
public CompatibilityInfo(ApplicationInfo appInfo) {
appFlags = appInfo.flags;
@@ -153,6 +127,7 @@
applicationScale =
DisplayMetrics.DEVICE_DENSITY / (float) DisplayMetrics.DEFAULT_DENSITY;
}
+
applicationInvertedScale = 1.0f / applicationScale;
if (applicationScale != 1.0f) {
mCompatibilityFlags |= SCALING_REQUIRED;
@@ -181,23 +156,10 @@
public CompatibilityInfo copy() {
CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags,
applicationScale, applicationInvertedScale);
- info.setVisibleRect(mXOffset, mWidth, mHeight);
return info;
}
/**
- * Sets the application's visible rect in compatibility mode.
- * @param xOffset the application's x offset that is added to center the content.
- * @param widthPixels the application's width in real pixels on the screen.
- * @param heightPixels the application's height in real pixels on the screen.
- */
- public void setVisibleRect(int xOffset, int widthPixels, int heightPixels) {
- this.mXOffset = xOffset;
- mWidth = widthPixels;
- mHeight = heightPixels;
- }
-
- /**
* Sets expandable bit in the compatibility flag.
*/
public void setExpandable(boolean expandable) {
@@ -222,6 +184,10 @@
return (mCompatibilityFlags & SCALING_REQUIRED) != 0;
}
+ public boolean supportsScreen() {
+ return (mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) != 0;
+ }
+
@Override
public String toString() {
return "CompatibilityInfo{scale=" + applicationScale +
@@ -231,21 +197,6 @@
/**
* Returns the translator which can translate the coordinates of the window.
* There are five different types of Translator.
- *
- * 1) {@link CompatibilityInfo#X_SHIFT_AND_CLIP_CONTENT}
- * Shift and clip the content of the window at drawing time. Used for activities'
- * main window (with no gravity).
- * 2) {@link CompatibilityInfo#X_SHIFT_CONTENT}
- * Shift the content of the window at drawing time. Used for windows that is created by
- * an application and expected to be aligned with the application window.
- * 3) {@link CompatibilityInfo#X_SHIFT_WINDOW}
- * Create the window with adjusted x- coordinates. This is typically used
- * in popup window, where it has to be placed relative to main window.
- * 4) {@link CompatibilityInfo#X_SHIFT_NONE}
- * No adjustment required, such as dialog.
- * 5) Same as X_SHIFT_WINDOW, but no scaling. This is used by {@link SurfaceView}, which
- * does not require scaling, but its window's location has to be adjusted.
- *
* @param params the window's parameter
*/
public Translator getTranslator(WindowManager.LayoutParams params) {
@@ -254,35 +205,11 @@
if (DBG) Log.d(TAG, "no translation required");
return null;
}
-
- if ((mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) == 0) {
- if ((params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) {
- if (DBG) Log.d(TAG, "translation for surface view selected");
- return new Translator(X_SHIFT_WINDOW, false, 1.0f, 1.0f);
- } else {
- int shiftMode;
- if (params.gravity == Gravity.NO_GRAVITY) {
- // For Regular Application window
- shiftMode = X_SHIFT_AND_CLIP_CONTENT;
- if (DBG) Log.d(TAG, "shift and clip translator");
- } else if (params.width == WindowManager.LayoutParams.FILL_PARENT) {
- // For Regular Application window
- shiftMode = X_SHIFT_CONTENT;
- if (DBG) Log.d(TAG, "shift content translator");
- } else if ((params.gravity & Gravity.LEFT) != 0 && params.x > 0) {
- shiftMode = X_SHIFT_WINDOW;
- if (DBG) Log.d(TAG, "shift window translator");
- } else {
- shiftMode = X_SHIFT_NONE;
- if (DBG) Log.d(TAG, "no content/window translator");
- }
- return new Translator(shiftMode);
- }
- } else if (isScalingRequired()) {
- return new Translator();
- } else {
+ if (!isScalingRequired() ||
+ (params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) {
return null;
}
+ return new Translator();
}
/**
@@ -290,97 +217,48 @@
* @hide
*/
public class Translator {
- final private int mShiftMode;
- final public boolean scalingRequired;
final public float applicationScale;
final public float applicationInvertedScale;
private Rect mContentInsetsBuffer = null;
- private Rect mVisibleInsets = null;
+ private Rect mVisibleInsetsBuffer = null;
- Translator(int shiftMode, boolean scalingRequired, float applicationScale,
- float applicationInvertedScale) {
- mShiftMode = shiftMode;
- this.scalingRequired = scalingRequired;
+ Translator(float applicationScale, float applicationInvertedScale) {
this.applicationScale = applicationScale;
this.applicationInvertedScale = applicationInvertedScale;
}
- Translator(int shiftMode) {
- this(shiftMode,
- isScalingRequired(),
- CompatibilityInfo.this.applicationScale,
- CompatibilityInfo.this.applicationInvertedScale);
- }
-
Translator() {
- this(X_SHIFT_NONE);
+ this(CompatibilityInfo.this.applicationScale,
+ CompatibilityInfo.this.applicationInvertedScale);
}
/**
* Translate the screen rect to the application frame.
*/
public void translateRectInScreenToAppWinFrame(Rect rect) {
- if (rect.isEmpty()) return; // skip if the window size is empty.
- switch (mShiftMode) {
- case X_SHIFT_AND_CLIP_CONTENT:
- rect.intersect(0, 0, mWidth, mHeight);
- break;
- case X_SHIFT_CONTENT:
- rect.intersect(0, 0, mWidth + mXOffset, mHeight);
- break;
- case X_SHIFT_WINDOW:
- case X_SHIFT_NONE:
- break;
- }
- if (scalingRequired) {
- rect.scale(applicationInvertedScale);
- }
+ rect.scale(applicationInvertedScale);
}
/**
* Translate the region in window to screen.
*/
public void translateRegionInWindowToScreen(Region transparentRegion) {
- switch (mShiftMode) {
- case X_SHIFT_AND_CLIP_CONTENT:
- case X_SHIFT_CONTENT:
- transparentRegion.scale(applicationScale);
- transparentRegion.translate(mXOffset, 0);
- break;
- case X_SHIFT_WINDOW:
- case X_SHIFT_NONE:
- transparentRegion.scale(applicationScale);
- }
+ transparentRegion.scale(applicationScale);
}
/**
* Apply translation to the canvas that is necessary to draw the content.
*/
public void translateCanvas(Canvas canvas) {
- if (mShiftMode == X_SHIFT_CONTENT ||
- mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
- // TODO: clear outside when rotation is changed.
-
- // Translate x-offset only when the content is shifted.
- canvas.translate(mXOffset, 0);
- }
- if (scalingRequired) {
- canvas.scale(applicationScale, applicationScale);
- }
+ canvas.scale(applicationScale, applicationScale);
}
/**
* Translate the motion event captured on screen to the application's window.
*/
public void translateEventInScreenToAppWindow(MotionEvent event) {
- if (mShiftMode == X_SHIFT_CONTENT ||
- mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
- event.translate(-mXOffset, 0);
- }
- if (scalingRequired) {
- event.scale(applicationInvertedScale);
- }
+ event.scale(applicationInvertedScale);
}
/**
@@ -388,62 +266,21 @@
* Screen's view.
*/
public void translateWindowLayout(WindowManager.LayoutParams params) {
- switch (mShiftMode) {
- case X_SHIFT_NONE:
- case X_SHIFT_AND_CLIP_CONTENT:
- case X_SHIFT_CONTENT:
- params.scale(applicationScale);
- break;
- case X_SHIFT_WINDOW:
- params.scale(applicationScale);
- params.x += mXOffset;
- break;
- }
+ params.scale(applicationScale);
}
/**
* Translate a Rect in application's window to screen.
*/
public void translateRectInAppWindowToScreen(Rect rect) {
- // TODO Auto-generated method stub
- if (scalingRequired) {
- rect.scale(applicationScale);
- }
- switch(mShiftMode) {
- case X_SHIFT_NONE:
- case X_SHIFT_WINDOW:
- break;
- case X_SHIFT_CONTENT:
- case X_SHIFT_AND_CLIP_CONTENT:
- rect.offset(mXOffset, 0);
- break;
- }
+ rect.scale(applicationScale);
}
/**
* Translate a Rect in screen coordinates into the app window's coordinates.
*/
public void translateRectInScreenToAppWindow(Rect rect) {
- switch (mShiftMode) {
- case X_SHIFT_NONE:
- case X_SHIFT_WINDOW:
- break;
- case X_SHIFT_CONTENT: {
- rect.intersects(mXOffset, 0, rect.right, rect.bottom);
- int dx = Math.min(mXOffset, rect.left);
- rect.offset(-dx, 0);
- break;
- }
- case X_SHIFT_AND_CLIP_CONTENT: {
- rect.intersects(mXOffset, 0, mWidth + mXOffset, mHeight);
- int dx = Math.min(mXOffset, rect.left);
- rect.offset(-dx, 0);
- break;
- }
- }
- if (scalingRequired) {
- rect.scale(applicationInvertedScale);
- }
+ rect.scale(applicationInvertedScale);
}
/**
@@ -451,19 +288,7 @@
* @param params
*/
public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
- if (scalingRequired) {
- params.scale(applicationScale);
- }
- switch (mShiftMode) {
- // the window location on these mode does not require adjustmenet.
- case X_SHIFT_NONE:
- case X_SHIFT_WINDOW:
- break;
- case X_SHIFT_CONTENT:
- case X_SHIFT_AND_CLIP_CONTENT:
- params.x += mXOffset;
- break;
- }
+ params.scale(applicationScale);
}
/**
@@ -482,10 +307,31 @@
* the internal buffer for content insets to avoid extra object allocation.
*/
public Rect getTranslatedVisbileInsets(Rect visibleInsets) {
- if (mVisibleInsets == null) mVisibleInsets = new Rect();
- mVisibleInsets.set(visibleInsets);
- translateRectInAppWindowToScreen(mVisibleInsets);
- return mVisibleInsets;
+ if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect();
+ mVisibleInsetsBuffer.set(visibleInsets);
+ translateRectInAppWindowToScreen(mVisibleInsetsBuffer);
+ return mVisibleInsetsBuffer;
+ }
+ }
+
+ /**
+ * Returns the frame Rect for applications runs under compatibility mode.
+ *
+ * @param dm the display metrics used to compute the frame size.
+ * @param orientation the orientation of the screen.
+ * @param outRect the output parameter which will contain the result.
+ */
+ public static void updateCompatibleScreenFrame(DisplayMetrics dm, int orientation,
+ Rect outRect) {
+ int width = dm.widthPixels;
+ int portraitHeight = (int) (DEFAULT_PORTRAIT_HEIGHT * dm.density);
+ int portraitWidth = (int) (DEFAULT_PORTRAIT_WIDTH * dm.density);
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ int xOffset = (width - portraitHeight) / 2 ;
+ outRect.set(xOffset, 0, xOffset + portraitHeight, portraitWidth);
+ } else {
+ int xOffset = (width - portraitWidth) / 2 ;
+ outRect.set(xOffset, 0, xOffset + portraitWidth, portraitHeight);
}
}
}
diff --git a/core/java/android/server/search/SearchDialogWrapper.java b/core/java/android/server/search/SearchDialogWrapper.java
new file mode 100644
index 0000000..dbc1e7f
--- /dev/null
+++ b/core/java/android/server/search/SearchDialogWrapper.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.search;
+
+import android.app.ISearchManagerCallback;
+import android.app.SearchDialog;
+import android.app.SearchManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Runs an instance of {@link SearchDialog} on its own thread.
+ */
+class SearchDialogWrapper
+implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
+
+ private static final String TAG = "SearchManagerService";
+ private static final boolean DBG = false;
+
+ private static final String DISABLE_SEARCH_PROPERTY = "dev.disablesearchdialog";
+
+ private static final String SEARCH_UI_THREAD_NAME = "SearchDialog";
+ private static final int SEARCH_UI_THREAD_PRIORITY =
+ android.os.Process.THREAD_PRIORITY_FOREGROUND;
+
+ // Takes no arguments
+ private static final int MSG_INIT = 0;
+ // Takes these arguments:
+ // arg1: selectInitialQuery, 0 = false, 1 = true
+ // arg2: globalSearch, 0 = false, 1 = true
+ // obj: searchManagerCallback
+ // data[KEY_INITIAL_QUERY]: initial query
+ // data[KEY_LAUNCH_ACTIVITY]: launch activity
+ // data[KEY_APP_SEARCH_DATA]: app search data
+ private static final int MSG_START_SEARCH = 1;
+ // Takes no arguments
+ private static final int MSG_STOP_SEARCH = 2;
+ // Takes no arguments
+ private static final int MSG_ON_CONFIGURATION_CHANGED = 3;
+
+ private static final String KEY_INITIAL_QUERY = "q";
+ private static final String KEY_LAUNCH_ACTIVITY = "a";
+ private static final String KEY_APP_SEARCH_DATA = "d";
+
+ // Context used for getting search UI resources
+ private final Context mContext;
+
+ // Handles messages on the search UI thread.
+ private final SearchDialogHandler mSearchUiThread;
+
+ // The search UI
+ SearchDialog mSearchDialog;
+
+ // If the search UI is visible, this is the callback for the client that showed it.
+ ISearchManagerCallback mCallback = null;
+
+ // Allows disabling of search dialog for stress testing runs
+ private final boolean mDisabledOnBoot;
+
+ /**
+ * Creates a new search dialog wrapper and a search UI thread. The search dialog itself will
+ * be created some asynchronously on the search UI thread.
+ *
+ * @param context Context used for getting search UI resources.
+ */
+ public SearchDialogWrapper(Context context) {
+ mContext = context;
+
+ mDisabledOnBoot = !TextUtils.isEmpty(SystemProperties.get(DISABLE_SEARCH_PROPERTY));
+
+ // Create the search UI thread
+ HandlerThread t = new HandlerThread(SEARCH_UI_THREAD_NAME, SEARCH_UI_THREAD_PRIORITY);
+ t.start();
+ mSearchUiThread = new SearchDialogHandler(t.getLooper());
+
+ // Create search UI on the search UI thread
+ mSearchUiThread.sendEmptyMessage(MSG_INIT);
+ }
+
+ /**
+ * Initializes the search UI.
+ * Must be called from the search UI thread.
+ */
+ private void init() {
+ mSearchDialog = new SearchDialog(mContext);
+ mSearchDialog.setOnCancelListener(this);
+ mSearchDialog.setOnDismissListener(this);
+ }
+
+ private void registerBroadcastReceiver() {
+ IntentFilter closeDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ mContext.registerReceiver(mBroadcastReceiver, closeDialogsFilter);
+ IntentFilter configurationChangedFilter =
+ new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
+ mContext.registerReceiver(mBroadcastReceiver, configurationChangedFilter);
+ }
+
+ private void unregisterBroadcastReceiver() {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ /**
+ * Closes the search dialog when requested by the system (e.g. when a phone call comes in).
+ */
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+ if (DBG) debug(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ stopSearch();
+ } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+ if (DBG) debug(Intent.ACTION_CONFIGURATION_CHANGED);
+ onConfigurationChanged();
+ }
+ }
+ };
+
+ //
+ // External API
+ //
+
+ /**
+ * Launches the search UI.
+ * Can be called from any thread.
+ *
+ * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
+ */
+ public void startSearch(final String initialQuery,
+ final boolean selectInitialQuery,
+ final ComponentName launchActivity,
+ final Bundle appSearchData,
+ final boolean globalSearch,
+ final ISearchManagerCallback searchManagerCallback) {
+ if (DBG) debug("startSearch()");
+ Message msg = Message.obtain();
+ msg.what = MSG_START_SEARCH;
+ msg.arg1 = selectInitialQuery ? 1 : 0;
+ msg.arg2 = globalSearch ? 1 : 0;
+ msg.obj = searchManagerCallback;
+ Bundle msgData = msg.getData();
+ msgData.putString(KEY_INITIAL_QUERY, initialQuery);
+ msgData.putParcelable(KEY_LAUNCH_ACTIVITY, launchActivity);
+ msgData.putBundle(KEY_APP_SEARCH_DATA, appSearchData);
+ mSearchUiThread.sendMessage(msg);
+ }
+
+ /**
+ * Cancels the search dialog.
+ * Can be called from any thread.
+ */
+ public void stopSearch() {
+ if (DBG) debug("stopSearch()");
+ mSearchUiThread.sendEmptyMessage(MSG_STOP_SEARCH);
+ }
+
+ /**
+ * Updates the search UI in response to a configuration change.
+ * Can be called from any thread.
+ */
+ void onConfigurationChanged() {
+ if (DBG) debug("onConfigurationChanged()");
+ mSearchUiThread.sendEmptyMessage(MSG_ON_CONFIGURATION_CHANGED);
+ }
+
+ //
+ // Implementation methods that run on the search UI thread
+ //
+
+ private class SearchDialogHandler extends Handler {
+
+ public SearchDialogHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INIT:
+ init();
+ break;
+ case MSG_START_SEARCH:
+ handleStartSearchMessage(msg);
+ break;
+ case MSG_STOP_SEARCH:
+ performStopSearch();
+ break;
+ case MSG_ON_CONFIGURATION_CHANGED:
+ performOnConfigurationChanged();
+ break;
+ }
+ }
+
+ private void handleStartSearchMessage(Message msg) {
+ Bundle msgData = msg.getData();
+ String initialQuery = msgData.getString(KEY_INITIAL_QUERY);
+ boolean selectInitialQuery = msg.arg1 != 0;
+ ComponentName launchActivity =
+ (ComponentName) msgData.getParcelable(KEY_LAUNCH_ACTIVITY);
+ Bundle appSearchData = msgData.getBundle(KEY_APP_SEARCH_DATA);
+ boolean globalSearch = msg.arg2 != 0;
+ ISearchManagerCallback searchManagerCallback = (ISearchManagerCallback) msg.obj;
+ performStartSearch(initialQuery, selectInitialQuery, launchActivity,
+ appSearchData, globalSearch, searchManagerCallback);
+ }
+
+ }
+
+ /**
+ * Actually launches the search UI.
+ * This must be called on the search UI thread.
+ */
+ void performStartSearch(String initialQuery,
+ boolean selectInitialQuery,
+ ComponentName launchActivity,
+ Bundle appSearchData,
+ boolean globalSearch,
+ ISearchManagerCallback searchManagerCallback) {
+ if (DBG) debug("performStartSearch()");
+
+ if (mDisabledOnBoot) {
+ Log.d(TAG, "ignoring start search request because " + DISABLE_SEARCH_PROPERTY
+ + " system property is set.");
+ return;
+ }
+
+ registerBroadcastReceiver();
+ mCallback = searchManagerCallback;
+ mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+ globalSearch);
+ }
+
+ /**
+ * Actually cancels the search UI.
+ * This must be called on the search UI thread.
+ */
+ void performStopSearch() {
+ if (DBG) debug("performStopSearch()");
+ mSearchDialog.cancel();
+ }
+
+ /**
+ * Must be called from the search UI thread.
+ */
+ void performOnConfigurationChanged() {
+ if (DBG) debug("performOnConfigurationChanged()");
+ mSearchDialog.onConfigurationChanged();
+ }
+
+ /**
+ * Called by {@link SearchDialog} when it goes away.
+ */
+ public void onDismiss(DialogInterface dialog) {
+ if (DBG) debug("onDismiss()");
+ if (mCallback != null) {
+ try {
+ // should be safe to do on the search UI thread, since it's a oneway interface
+ mCallback.onDismiss();
+ } catch (DeadObjectException ex) {
+ // The process that hosted the callback has died, do nothing
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onDismiss() failed: " + ex);
+ }
+ // we don't need the callback anymore, release it
+ mCallback = null;
+ }
+ unregisterBroadcastReceiver();
+ }
+
+ /**
+ * Called by {@link SearchDialog} when the user or activity cancels search.
+ * Whenever this method is called, {@link #onDismiss} is always called afterwards.
+ */
+ public void onCancel(DialogInterface dialog) {
+ if (DBG) debug("onCancel()");
+ if (mCallback != null) {
+ try {
+ // should be safe to do on the search UI thread, since it's a oneway interface
+ mCallback.onCancel();
+ } catch (DeadObjectException ex) {
+ // The process that hosted the callback has died, do nothing
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onCancel() failed: " + ex);
+ }
+ }
+ }
+
+ private static void debug(String msg) {
+ Thread thread = Thread.currentThread();
+ Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+ }
+}
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 373e61f..87adfb3 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -18,52 +18,38 @@
import android.app.ISearchManager;
import android.app.ISearchManagerCallback;
-import android.app.SearchDialog;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.text.TextUtils;
import android.util.Log;
import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
/**
- * This is a simplified version of the Search Manager service. It no longer handles
- * presentation (UI). Its function is to maintain the map & list of "searchable"
- * items, which provides a mapping from individual activities (where a user might have
- * invoked search) to specific searchable activities (where the search will be dispatched).
+ * The search manager service handles the search UI, and maintains a registry of searchable
+ * activities.
*/
-public class SearchManagerService extends ISearchManager.Stub
- implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener
-{
- // general debugging support
+public class SearchManagerService extends ISearchManager.Stub {
+
+ // general debugging support
private static final String TAG = "SearchManagerService";
private static final boolean DBG = false;
- // class maintenance and general shared data
+ // Context that the service is running in.
private final Context mContext;
- private final Handler mHandler;
- private boolean mSearchablesDirty;
- private final Searchables mSearchables;
- final SearchDialog mSearchDialog;
- ISearchManagerCallback mCallback = null;
+ // This field is initialized in initialize(), and then never modified.
+ // It is volatile since it can be accessed by multiple threads.
+ private volatile Searchables mSearchables;
- private final boolean mDisabledOnBoot;
-
- private static final String DISABLE_SEARCH_PROPERTY = "dev.disablesearchdialog";
+ // This field is initialized in initialize(), and then never modified.
+ // It is volatile since it can be accessed by multiple threads.
+ private volatile SearchDialogWrapper mSearchDialog;
/**
* Initializes the Search Manager service in the provided system context.
@@ -73,82 +59,71 @@
*/
public SearchManagerService(Context context) {
mContext = context;
- mHandler = new Handler();
- mSearchablesDirty = true;
- mSearchables = new Searchables(context);
- mSearchDialog = new SearchDialog(context);
- mSearchDialog.setOnCancelListener(this);
- mSearchDialog.setOnDismissListener(this);
-
- // Setup the infrastructure for updating and maintaining the list
- // of searchable activities.
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addDataScheme("package");
- mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
-
- // After startup settles down, preload the searchables list,
- // which will reduce the delay when the search UI is invoked.
- mHandler.post(mRunUpdateSearchable);
-
- // allows disabling of search dialog for stress testing runs
- mDisabledOnBoot = !TextUtils.isEmpty(SystemProperties.get(DISABLE_SEARCH_PROPERTY));
+ // call initialize() after all pending actions on the main system thread have finished
+ new Handler().post(new Runnable() {
+ public void run() {
+ initialize();
+ }
+ });
}
/**
- * Listens for intent broadcasts.
- *
- * The primary purpose here is to refresh the "searchables" list
- * if packages are added/removed.
+ * Initializes the search UI and the list of searchable activities.
*/
- private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ void initialize() {
+ mSearchables = createSearchables();
+ mSearchDialog = new SearchDialogWrapper(mContext);
+ }
+
+ private Searchables createSearchables() {
+ Searchables searchables = new Searchables(mContext);
+ searchables.buildSearchableList();
+
+ IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ packageFilter.addDataScheme("package");
+ mContext.registerReceiver(mPackageChangedReceiver, packageFilter);
+
+ return searchables;
+ }
+
+ /**
+ * Refreshes the "searchables" list when packages are added/removed.
+ */
+ private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- // First, test for intents that matter at any time
- if (action.equals(Intent.ACTION_PACKAGE_ADDED) ||
- action.equals(Intent.ACTION_PACKAGE_REMOVED) ||
- action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
- mSearchablesDirty = true;
- mHandler.post(mRunUpdateSearchable);
- return;
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
+ Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
+ Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ if (DBG) Log.d(TAG, "Got " + action);
+ // Dismiss search dialog, since the search context may no longer be valid
+ mSearchDialog.stopSearch();
+ // Update list of searchable activities
+ mSearchables.buildSearchableList();
+ broadcastSearchablesChanged();
}
}
};
/**
- * This runnable (for the main handler / UI thread) will update the searchables list.
+ * Informs all listeners that the list of searchables has been updated.
*/
- private Runnable mRunUpdateSearchable = new Runnable() {
- public void run() {
- updateSearchablesIfDirty();
- }
- };
-
- /**
- * Updates the list of searchables, either at startup or in response to
- * a package add/remove broadcast message.
- */
- private void updateSearchables() {
- if (DBG) debug("updateSearchables()");
- mSearchables.buildSearchableList();
- mSearchablesDirty = false;
+ void broadcastSearchablesChanged() {
+ mContext.sendBroadcast(
+ new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));
}
- /**
- * Updates the list of searchables if needed.
- */
- private void updateSearchablesIfDirty() {
- if (mSearchablesDirty) {
- updateSearchables();
- }
- }
+ //
+ // Searchable activities API
+ //
/**
- * Returns the SearchableInfo for a given activity
+ * 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
@@ -158,226 +133,84 @@
* @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available.
*/
- public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) {
- updateSearchablesIfDirty();
- SearchableInfo si = null;
+ public SearchableInfo getSearchableInfo(final ComponentName launchActivity,
+ final boolean globalSearch) {
+ if (mSearchables == null) return null;
if (globalSearch) {
- si = mSearchables.getDefaultSearchable();
+ return mSearchables.getDefaultSearchable();
} else {
if (launchActivity == null) {
Log.e(TAG, "getSearchableInfo(), activity == null");
return null;
}
- si = mSearchables.getSearchableInfo(launchActivity);
+ return mSearchables.getSearchableInfo(launchActivity);
}
-
- return si;
}
/**
* Returns a list of the searchable activities that can be included in global search.
*/
public List<SearchableInfo> getSearchablesInGlobalSearch() {
- updateSearchablesIfDirty();
+ if (mSearchables == null) return null;
return mSearchables.getSearchablesInGlobalSearchList();
}
- /**
- * Launches the search UI on the main thread of the service.
- *
- * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
- */
- public void startSearch(final String initialQuery,
- final boolean selectInitialQuery,
- final ComponentName launchActivity,
- final Bundle appSearchData,
- final boolean globalSearch,
- final ISearchManagerCallback searchManagerCallback) {
- if (DBG) debug("startSearch()");
- Runnable task = new Runnable() {
- public void run() {
- performStartSearch(initialQuery,
- selectInitialQuery,
- launchActivity,
- appSearchData,
- globalSearch,
- searchManagerCallback);
- }
- };
- mHandler.post(task);
- }
/**
- * Actually launches the search. This must be called on the service UI thread.
+ * Returns a list of the searchable activities that handle web searches.
+ * Can be called from any thread.
*/
- /*package*/ void performStartSearch(String initialQuery,
+ public List<SearchableInfo> getSearchablesForWebSearch() {
+ if (mSearchables == null) return null;
+ return mSearchables.getSearchablesForWebSearchList();
+ }
+
+ /**
+ * Returns the default searchable activity for web searches.
+ * Can be called from any thread.
+ */
+ public SearchableInfo getDefaultSearchableForWebSearch() {
+ if (mSearchables == null) return null;
+ return mSearchables.getDefaultSearchableForWebSearch();
+ }
+
+ /**
+ * Sets the default searchable activity for web searches.
+ * Can be called from any thread.
+ */
+ public void setDefaultWebSearch(final ComponentName component) {
+ if (mSearchables == null) return;
+ mSearchables.setDefaultWebSearch(component);
+ broadcastSearchablesChanged();
+ }
+
+ // Search UI API
+
+ /**
+ * Launches the search UI. Can be called from any thread.
+ *
+ * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
+ */
+ public void startSearch(String initialQuery,
boolean selectInitialQuery,
ComponentName launchActivity,
Bundle appSearchData,
boolean globalSearch,
ISearchManagerCallback searchManagerCallback) {
- if (DBG) debug("performStartSearch()");
-
- if (mDisabledOnBoot) {
- Log.d(TAG, "ignoring start search request because " + DISABLE_SEARCH_PROPERTY
- + " system property is set.");
- return;
- }
-
- mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
- globalSearch);
- if (searchManagerCallback != null) {
- mCallback = searchManagerCallback;
- }
+ if (mSearchDialog == null) return;
+ mSearchDialog.startSearch(initialQuery,
+ selectInitialQuery,
+ launchActivity,
+ appSearchData,
+ globalSearch,
+ searchManagerCallback);
}
/**
* Cancels the search dialog. Can be called from any thread.
*/
public void stopSearch() {
- if (DBG) debug("stopSearch()");
- mHandler.post(new Runnable() {
- public void run() {
- performStopSearch();
- }
- });
- }
-
- /**
- * Cancels the search dialog. Must be called from the service UI thread.
- */
- /*package*/ void performStopSearch() {
- if (DBG) debug("performStopSearch()");
- mSearchDialog.cancel();
- }
-
- /**
- * Determines if the Search UI is currently displayed.
- *
- * @see SearchManager#isVisible()
- */
- public boolean isVisible() {
- return postAndWait(mIsShowing, false, "isShowing()");
- }
-
- private final Callable<Boolean> mIsShowing = new Callable<Boolean>() {
- public Boolean call() {
- return mSearchDialog.isShowing();
- }
- };
-
- public Bundle onSaveInstanceState() {
- return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()");
- }
-
- private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() {
- public Bundle call() {
- if (mSearchDialog.isShowing()) {
- return mSearchDialog.onSaveInstanceState();
- } else {
- return null;
- }
- }
- };
-
- public void onRestoreInstanceState(final Bundle searchDialogState) {
- if (searchDialogState != null) {
- mHandler.post(new Runnable() {
- public void run() {
- mSearchDialog.onRestoreInstanceState(searchDialogState);
- }
- });
- }
- }
-
- public void onConfigurationChanged(final Configuration newConfig) {
- mHandler.post(new Runnable() {
- public void run() {
- if (mSearchDialog.isShowing()) {
- mSearchDialog.onConfigurationChanged(newConfig);
- }
- }
- });
- }
-
- /**
- * Called by {@link SearchDialog} when it goes away.
- */
- public void onDismiss(DialogInterface dialog) {
- if (DBG) debug("onDismiss()");
- if (mCallback != null) {
- try {
- mCallback.onDismiss();
- } catch (RemoteException ex) {
- Log.e(TAG, "onDismiss() failed: " + ex);
- }
- }
- }
-
- /**
- * Called by {@link SearchDialog} when the user or activity cancels search.
- * When this is called, {@link #onDismiss} is called too.
- */
- public void onCancel(DialogInterface dialog) {
- if (DBG) debug("onCancel()");
- if (mCallback != null) {
- try {
- mCallback.onCancel();
- } catch (RemoteException ex) {
- Log.e(TAG, "onCancel() failed: " + ex);
- }
- }
- }
-
- /**
- * Returns a list of the searchable activities that handle web searches.
- */
- public List<SearchableInfo> getSearchablesForWebSearch() {
- updateSearchablesIfDirty();
- return mSearchables.getSearchablesForWebSearchList();
- }
-
- /**
- * Returns the default searchable activity for web searches.
- */
- public SearchableInfo getDefaultSearchableForWebSearch() {
- updateSearchablesIfDirty();
- return mSearchables.getDefaultSearchableForWebSearch();
- }
-
- /**
- * Sets the default searchable activity for web searches.
- */
- public void setDefaultWebSearch(ComponentName component) {
- mSearchables.setDefaultWebSearch(component);
- }
-
- /**
- * Runs an operation on the handler for the service, blocks until it returns,
- * and returns the value returned by the operation.
- *
- * @param <V> Return value type.
- * @param callable Operation to run.
- * @param errorResult Value to return if the operations throws an exception.
- * @param name Operation name to include in error log messages.
- * @return The value returned by the operation.
- */
- private <V> V postAndWait(Callable<V> callable, V errorResult, String name) {
- FutureTask<V> task = new FutureTask<V>(callable);
- mHandler.post(task);
- try {
- return task.get();
- } catch (InterruptedException ex) {
- Log.e(TAG, "Error calling " + name + ": " + ex);
- return errorResult;
- } catch (ExecutionException ex) {
- Log.e(TAG, "Error calling " + name + ": " + ex);
- return errorResult;
- }
- }
-
- private static void debug(String msg) {
- Thread thread = Thread.currentThread();
- Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+ if (mSearchDialog == null) return;
+ mSearchDialog.stopSearch();
}
}
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 8ef1f15..283555a 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -67,6 +67,7 @@
private final int mSearchImeOptions;
private final boolean mIncludeInGlobalSearch;
private final boolean mQueryAfterZeroResults;
+ private final boolean mAutoUrlDetect;
private final String mSettingsDescription;
private final String mSuggestAuthority;
private final String mSuggestPath;
@@ -288,6 +289,8 @@
com.android.internal.R.styleable.Searchable_includeInGlobalSearch, false);
mQueryAfterZeroResults = a.getBoolean(
com.android.internal.R.styleable.Searchable_queryAfterZeroResults, false);
+ mAutoUrlDetect = a.getBoolean(
+ com.android.internal.R.styleable.Searchable_autoUrlDetect, false);
mSettingsDescription = a.getString(
com.android.internal.R.styleable.Searchable_searchSettingsDescription);
@@ -667,6 +670,16 @@
}
/**
+ * Checks whether this searchable activity has auto URL detect turned on.
+ *
+ * @return The value of the <code>autoUrlDetect</code> attribute,
+ * or <code>false</code> if the attribute is not set.
+ */
+ public boolean autoUrlDetect() {
+ return mAutoUrlDetect;
+ }
+
+ /**
* Support for parcelable and aidl operations.
*/
public static final Parcelable.Creator<SearchableInfo> CREATOR
@@ -698,6 +711,7 @@
mSearchImeOptions = in.readInt();
mIncludeInGlobalSearch = in.readInt() != 0;
mQueryAfterZeroResults = in.readInt() != 0;
+ mAutoUrlDetect = in.readInt() != 0;
mSettingsDescription = in.readString();
mSuggestAuthority = in.readString();
@@ -735,6 +749,7 @@
dest.writeInt(mSearchImeOptions);
dest.writeInt(mIncludeInGlobalSearch ? 1 : 0);
dest.writeInt(mQueryAfterZeroResults ? 1 : 0);
+ dest.writeInt(mAutoUrlDetect ? 1 : 0);
dest.writeString(mSettingsDescription);
dest.writeString(mSuggestAuthority);
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
index c7cc8ed..b959907 100644
--- a/core/java/android/server/search/Searchables.java
+++ b/core/java/android/server/search/Searchables.java
@@ -17,7 +17,6 @@
package android.server.search;
import com.android.internal.app.ResolverActivity;
-import com.android.internal.R;
import android.app.SearchManager;
import android.content.ComponentName;
@@ -27,7 +26,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
@@ -264,7 +262,7 @@
}
// Find the default web search provider.
- ComponentName webSearchActivity = getPreferredWebSearchActivity();
+ ComponentName webSearchActivity = getPreferredWebSearchActivity(mContext);
SearchableInfo newDefaultSearchableForWebSearch = null;
if (webSearchActivity != null) {
newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity);
@@ -283,9 +281,6 @@
mDefaultSearchable = newDefaultSearchable;
mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch;
}
-
- // Inform all listeners that the list of searchables has been updated.
- mContext.sendBroadcast(new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));
}
/**
@@ -295,9 +290,10 @@
* @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.
*/
- private boolean setPreferredActivity(ComponentName component, String action) {
+ private static boolean setPreferredActivity(Context context,
+ ComponentName component, String action) {
Log.d(LOG_TAG, "Checking component " + component);
- PackageManager pm = mContext.getPackageManager();
+ PackageManager pm = context.getPackageManager();
ActivityInfo ai;
try {
ai = pm.getActivityInfo(component, 0);
@@ -326,10 +322,10 @@
return true;
}
- public ComponentName getPreferredWebSearchActivity() {
+ 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 = mContext.getPackageManager();
+ PackageManager pm = context.getPackageManager();
ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) {
@@ -338,11 +334,11 @@
// 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 = mContext.getResources().getStringArray(
+ 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(component, Intent.ACTION_WEB_SEARCH)) {
+ if (setPreferredActivity(context, component, Intent.ACTION_WEB_SEARCH)) {
return component;
}
}
@@ -354,7 +350,8 @@
if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) {
ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString(
ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME);
- if (setPreferredActivity(enhancedGoogleSearch, Intent.ACTION_WEB_SEARCH)) {
+ if (setPreferredActivity(context, enhancedGoogleSearch,
+ Intent.ACTION_WEB_SEARCH)) {
return enhancedGoogleSearch;
}
}
@@ -397,7 +394,7 @@
* Sets the default searchable activity for web searches.
*/
public synchronized void setDefaultWebSearch(ComponentName component) {
- setPreferredActivity(component, Intent.ACTION_WEB_SEARCH);
+ setPreferredActivity(mContext, component, Intent.ACTION_WEB_SEARCH);
buildSearchableList();
}
}
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 616b3f1..ed1e4ff6 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -107,9 +107,7 @@
public static final int FALLBACK_TTS_DEFAULT_RATE = 100; // 1x
public static final int FALLBACK_TTS_DEFAULT_PITCH = 100;// 1x
public static final int FALLBACK_TTS_USE_DEFAULTS = 0; // false
- public static final String FALLBACK_TTS_DEFAULT_LANG = "eng";
- public static final String FALLBACK_TTS_DEFAULT_COUNTRY = "";
- public static final String FALLBACK_TTS_DEFAULT_VARIANT = "";
+ public static final String FALLBACK_TTS_DEFAULT_SYNTH = "com.svox.pico";
// return codes for a TTS engine's check data activity
public static final int CHECK_VOICE_DATA_PASS = 1;
@@ -608,6 +606,7 @@
result = mITts.setLanguage(mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1],
mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1],
mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT + 1] );
+ } catch (RemoteException e) {
// TTS died; restart it.
mStarted = false;
initTts();
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 4179edb..9071bf0 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -109,7 +109,6 @@
*/
public void updateMetrics(CompatibilityInfo compatibilityInfo, int orientation,
int screenLayout) {
- int xOffset = 0;
if (!compatibilityInfo.isConfiguredExpandable()) {
// Note: this assume that configuration is updated before calling
// updateMetrics method.
@@ -142,7 +141,6 @@
if (defaultWidth < widthPixels) {
// content/window's x offset in original pixels
- xOffset = ((widthPixels - defaultWidth) / 2);
widthPixels = defaultWidth;
}
if (defaultHeight < heightPixels) {
@@ -154,7 +152,6 @@
compatibilityInfo.setExpandable(true);
}
}
- compatibilityInfo.setVisibleRect(xOffset, widthPixels, heightPixels);
if (compatibilityInfo.isScalingRequired()) {
float invertedRatio = compatibilityInfo.applicationInvertedScale;
density *= invertedRatio;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 45b0f0a7..ff1eb53 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -306,7 +306,7 @@
// Use original size if the app specified the size of the view,
// and let the flinger to scale up.
- if (mRequestedWidth <= 0 && mTranslator != null && mTranslator.scalingRequired) {
+ if (mRequestedWidth <= 0 && mTranslator != null) {
myWidth *= appScale;
myHeight *= appScale;
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 6f6e2247..6bcb135 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -385,6 +385,7 @@
if (mView == null) {
mView = view;
mWindowAttributes.copyFrom(attrs);
+ attrs = mWindowAttributes;
CompatibilityInfo compatibilityInfo =
mView.getContext().getResources().getCompatibilityInfo();
@@ -397,11 +398,14 @@
}
if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs);
+ if (!compatibilityInfo.supportsScreen()) {
+ attrs.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
+ }
+
mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mAttachInfo.mRootView = view;
- mAttachInfo.mScalingRequired =
- mTranslator == null ? false : mTranslator.scalingRequired;
+ mAttachInfo.mScalingRequired = mTranslator == null ? false : true;
mAttachInfo.mApplicationScale =
mTranslator == null ? 1.0f : mTranslator.applicationScale;
if (panelParentView != null) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index bdb86d7..e96a15b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -484,11 +484,19 @@
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
/** Window flag: special flag to let a window ignore the compatibility scaling.
- * This is used by SurfaceView to create a window that does not scale the content.
+ * This is used by SurfaceView to pass this info into ViewRoot, and not used
+ * by WindowManager.
*
* {@hide} */
public static final int FLAG_NO_COMPATIBILITY_SCALING = 0x00100000;
+ /** Window flag: special flag to limit the size of the window to be
+ * original size ([320x480] x density). Used to create window for applications
+ * running under compatibility mode.
+ *
+ * {@hide} */
+ public static final int FLAG_COMPATIBLE_WINDOW = 0x00200000;
+
/** Window flag: a special option intended for system dialogs. When
* this flag is set, the window will demand focus unconditionally when
* it is created.
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 670692f..df957ac 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -65,6 +65,7 @@
*/
public static final int LENGTH_LONG = 1;
+ final Handler mHandler = new Handler();
final Context mContext;
final TN mTN;
int mDuration;
@@ -84,7 +85,7 @@
*/
public Toast(Context context) {
mContext = context;
- mTN = new TN(context);
+ mTN = new TN();
mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
}
@@ -229,7 +230,8 @@
public static Toast makeText(Context context, CharSequence text, int duration) {
Toast result = new Toast(context);
- LayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ LayoutInflater inflate = (LayoutInflater)
+ context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
@@ -286,8 +288,7 @@
private static INotificationManager sService;
- static private INotificationManager getService()
- {
+ static private INotificationManager getService() {
if (sService != null) {
return sService;
}
@@ -295,28 +296,42 @@
return sService;
}
- private class TN extends ITransientNotification.Stub
- {
- TN(Context context)
- {
+ private class TN extends ITransientNotification.Stub {
+ final Runnable mShow = new Runnable() {
+ public void run() {
+ handleShow();
+ }
+ };
+
+ final Runnable mHide = new Runnable() {
+ public void run() {
+ handleHide();
+ }
+ };
+
+ private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+
+ WindowManagerImpl mWM;
+
+ TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
- mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
- mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
- mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ final WindowManager.LayoutParams params = mParams;
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
- mParams.format = PixelFormat.TRANSLUCENT;
- mParams.windowAnimations = com.android.internal.R.style.Animation_Toast;
- mParams.type = WindowManager.LayoutParams.TYPE_TOAST;
- mParams.setTitle("Toast");
+ params.format = PixelFormat.TRANSLUCENT;
+ params.windowAnimations = com.android.internal.R.style.Animation_Toast;
+ params.type = WindowManager.LayoutParams.TYPE_TOAST;
+ params.setTitle("Toast");
}
/**
* schedule handleShow into the right thread
*/
- public void show()
- {
+ public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
@@ -324,14 +339,12 @@
/**
* schedule handleHide into the right thread
*/
- public void hide()
- {
+ public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
- public void handleShow()
- {
+ public void handleShow() {
if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
+ " mNextView=" + mNextView);
if (mView != mNextView) {
@@ -361,8 +374,7 @@
}
}
- public void handleHide()
- {
+ public void handleHide() {
if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
if (mView != null) {
// note: checking parent() just to make sure the view has
@@ -377,24 +389,5 @@
mView = null;
}
}
-
- Runnable mShow = new Runnable() {
- public void run() {
- handleShow();
- }
- };
-
- Runnable mHide = new Runnable() {
- public void run() {
- handleHide();
- }
- };
-
- private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
-
- WindowManagerImpl mWM;
}
-
- final Handler mHandler = new Handler();
}
-
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 77a8a72..57b5aa6 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -55,6 +55,7 @@
~JNICameraContext() { release(); }
virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);
virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr);
+ virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
sp<Camera> getCamera() { Mutex::Autolock _l(mLock); return mCamera; }
void release();
@@ -188,6 +189,12 @@
}
}
+void JNICameraContext::postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr)
+{
+ // TODO: plumb up to Java. For now, just drop the timestamp
+ postData(msgType, dataPtr);
+}
+
// connect to camera service
static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 599360f..23967f4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -995,6 +995,29 @@
android:description="@string/permdesc_changeBackgroundDataSetting"
android:label="@string/permlab_changeBackgroundDataSetting" />
+ <!-- This permission can be used on content providers to allow the global
+ search system to access their data. Typically it used when the
+ provider has some permissions protecting it (which global search
+ would not be expected to hold), and added as a read-only permission
+ to the path in the provider where global search queries are
+ performed. This permission can not be held by regular applications;
+ it is used by applications to protect themselves from everyone else
+ besides global search. -->
+ <permission android:name="android.permission.GLOBAL_SEARCH"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signatureOrSystem" />
+
+ <!-- Internal permission protecting access to the global search
+ system: ensures that only the system can access the provider
+ to perform queries (since this otherwise provides unrestricted
+ access to a variety of content providers), and to write the
+ search statistics (to keep applications from gaming the source
+ ranking).
+ @hide -->
+ <permission android:name="android.permission.GLOBAL_SEARCH_CONTROL"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6f2a5d3..fd78f83 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2888,6 +2888,14 @@
attribute.</i> -->
<attr name="searchSettingsDescription" format="string" />
+ <!-- If provided and <code>true</code>, URLs entered in the search dialog while searching
+ within this activity would be detected and treated as URLs (show a 'go' button in the
+ keyboard and invoke the browser directly when user launches the URL instead of passing
+ the URL to the activity). If set to <code>false</code> any URLs entered are treated as
+ normal query text.
+ The default value is <code>false</code>. <i>Optional attribute.</i>. -->
+ <attr name="autoUrlDetect" format="boolean" />
+
</declare-styleable>
<!-- In order to process special action keys during search, you must define them using
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 7571e24..12a76ba 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -953,6 +953,20 @@
<attr name="pathPattern" format="string" />
</declare-styleable>
+ <!-- Attributes that can be supplied in an AndroidManifest.xml
+ <code>path-permission</code> tag, a child of the
+ {@link #AndroidManifestProvider provider} tag, describing a permission
+ that allows access to a specific path in the provider. This tag can be
+ specified multiple time to supply multiple paths. -->
+ <declare-styleable name="AndroidManifestPathPermission" parent="AndroidManifestProvider">
+ <attr name="path" />
+ <attr name="pathPrefix" />
+ <attr name="pathPattern" />
+ <attr name="permission" />
+ <attr name="readPermission" />
+ <attr name="writePermission" />
+ </declare-styleable>
+
<!-- The <code>service</code> tag declares a
{@link android.app.Service} class that is available
as part of the package's application components, implementing
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 32c6937..871c651 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1125,6 +1125,7 @@
<public type="attr" name="progressBarStyleLargeInverse" />
<public type="attr" name="searchSettingsDescription" />
<public type="attr" name="textColorPrimaryInverseDisableOnly" />
+ <public type="attr" name="autoUrlDetect" />
<public-padding type="attr" name="donut_resource_pad" end="0x0101029f" />
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 0bd3276..a3579c7 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -143,8 +143,6 @@
<!-- This is a list of all the libraries available for application
code to link against. -->
- <library name="android.awt"
- file="/system/framework/android.awt.jar" />
<library name="android.test.runner"
file="/system/framework/android.test.runner.jar" />
<library name="com.android.im.plugin"
diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index d24194f..6677a35 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -278,10 +278,15 @@
if (name.equals("padding")) {
TypedArray a = r.obtainAttributes(attrs,
com.android.internal.R.styleable.ShapeDrawablePadding);
- setPadding(a.getInt(com.android.internal.R.styleable.ShapeDrawablePadding_left, 0),
- a.getInt(com.android.internal.R.styleable.ShapeDrawablePadding_top, 0),
- a.getInt(com.android.internal.R.styleable.ShapeDrawablePadding_right, 0),
- a.getInt(com.android.internal.R.styleable.ShapeDrawablePadding_bottom, 0));
+ setPadding(
+ a.getDimensionPixelOffset(
+ com.android.internal.R.styleable.ShapeDrawablePadding_left, 0),
+ a.getDimensionPixelOffset(
+ com.android.internal.R.styleable.ShapeDrawablePadding_top, 0),
+ a.getDimensionPixelOffset(
+ com.android.internal.R.styleable.ShapeDrawablePadding_right, 0),
+ a.getDimensionPixelOffset(
+ com.android.internal.R.styleable.ShapeDrawablePadding_bottom, 0));
a.recycle();
return true;
}
diff --git a/include/ui/Camera.h b/include/ui/Camera.h
index e3544ab..afb07b5 100644
--- a/include/ui/Camera.h
+++ b/include/ui/Camera.h
@@ -18,6 +18,7 @@
#ifndef ANDROID_HARDWARE_CAMERA_H
#define ANDROID_HARDWARE_CAMERA_H
+#include <utils/Timers.h>
#include <ui/ICameraClient.h>
namespace android {
@@ -94,6 +95,7 @@
public:
virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2) = 0;
virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr) = 0;
+ virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) = 0;
};
class Camera : public BnCameraClient, public IBinder::DeathRecipient
@@ -155,6 +157,7 @@
// ICameraClient interface
virtual void notifyCallback(int32_t msgType, int32_t ext, int32_t ext2);
virtual void dataCallback(int32_t msgType, const sp<IMemory>& dataPtr);
+ virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
sp<ICamera> remote();
diff --git a/include/ui/CameraHardwareInterface.h b/include/ui/CameraHardwareInterface.h
index 73036f0..822b4a8 100644
--- a/include/ui/CameraHardwareInterface.h
+++ b/include/ui/CameraHardwareInterface.h
@@ -28,7 +28,7 @@
typedef void (*preview_callback)(const sp<IMemory>& mem, void* user);
/** Callback for startRecord() */
-typedef void (*recording_callback)(const sp<IMemory>& mem, void* user);
+typedef void (*recording_callback)(nsecs_t timestamp, const sp<IMemory>& mem, void* user);
/** Callback for takePicture() */
typedef void (*shutter_callback)(void* user);
diff --git a/include/ui/ICameraClient.h b/include/ui/ICameraClient.h
index c4bdd07..1001c71 100644
--- a/include/ui/ICameraClient.h
+++ b/include/ui/ICameraClient.h
@@ -21,6 +21,7 @@
#include <utils/IInterface.h>
#include <utils/Parcel.h>
#include <utils/IMemory.h>
+#include <utils/Timers.h>
namespace android {
@@ -31,7 +32,7 @@
virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) = 0;
virtual void dataCallback(int32_t msgType, const sp<IMemory>& data) = 0;
-
+ virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& data) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/utils/String8.h b/include/utils/String8.h
index c49faf6..ecc5774 100644
--- a/include/utils/String8.h
+++ b/include/utils/String8.h
@@ -29,11 +29,107 @@
// ---------------------------------------------------------------------------
+extern "C" {
+
+typedef uint32_t char32_t;
+
+size_t strlen32(const char32_t *);
+size_t strnlen32(const char32_t *, size_t);
+
+/*
+ * Returns the length of "src" when "src" is valid UTF-8 string.
+ * Returns 0 if src is NULL, 0-length string or non UTF-8 string.
+ * This function should be used to determine whether "src" is valid UTF-8
+ * characters with valid unicode codepoints. "src" must be null-terminated.
+ *
+ * If you are going to use other GetUtf... functions defined in this header
+ * with string which may not be valid UTF-8 with valid codepoint (form 0 to
+ * 0x10FFFF), you should use this function before calling others, since the
+ * other functions do not check whether the string is valid UTF-8 or not.
+ *
+ * If you do not care whether "src" is valid UTF-8 or not, you should use
+ * strlen() as usual, which should be much faster.
+ */
+size_t utf8_length(const char *src);
+
+/*
+ * Returns the UTF-32 length of "src".
+ */
+size_t utf32_length(const char *src, size_t src_len);
+
+/*
+ * Returns the UTF-8 length of "src".
+ */
+size_t utf8_length_from_utf32(const char32_t *src, size_t src_len);
+
+/*
+ * Returns the unicode value at "index".
+ * Returns -1 when the index is invalid (equals to or more than "src_len").
+ * If returned value is positive, it is able to be converted to char32_t, which
+ * is unsigned. Then, if "next_index" is not NULL, the next index to be used is
+ * stored in "next_index". "next_index" can be NULL.
+ */
+int32_t utf32_at(const char *src, size_t src_len,
+ size_t index, size_t *next_index);
+
+/*
+ * Stores a UTF-32 string converted from "src" in "dst", if "dst_length" is not
+ * large enough to store the string, the part of the "src" string is stored
+ * into "dst".
+ * Returns the size actually used for storing the string.
+ * "dst" is not null-terminated when dst_len is fully used (like strncpy).
+ */
+size_t utf8_to_utf32(const char* src, size_t src_len,
+ char32_t* dst, size_t dst_len);
+
+/*
+ * Stores a UTF-8 string converted from "src" in "dst", if "dst_length" is not
+ * large enough to store the string, the part of the "src" string is stored
+ * into "dst" as much as possible. See the examples for more detail.
+ * Returns the size actually used for storing the string.
+ * dst" is not null-terminated when dst_len is fully used (like strncpy).
+ *
+ * Example 1
+ * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
+ * "src_len" == 2
+ * "dst_len" >= 7
+ * ->
+ * Returned value == 6
+ * "dst" becomes \xE3\x81\x82\xE3\x81\x84\0
+ * (note that "dst" is null-terminated)
+ *
+ * Example 2
+ * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
+ * "src_len" == 2
+ * "dst_len" == 5
+ * ->
+ * Returned value == 3
+ * "dst" becomes \xE3\x81\x82\0
+ * (note that "dst" is null-terminated, but \u3044 is not stored in "dst"
+ * since "dst" does not have enough size to store the character)
+ *
+ * Example 3
+ * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
+ * "src_len" == 2
+ * "dst_len" == 6
+ * ->
+ * Returned value == 6
+ * "dst" becomes \xE3\x81\x82\xE3\x81\x84
+ * (note that "dst" is NOT null-terminated, like strncpy)
+ */
+size_t utf32_to_utf8(const char32_t* src, size_t src_len,
+ char* dst, size_t dst_len);
+
+}
+
+// ---------------------------------------------------------------------------
+
namespace android {
class TextOutput;
-//! This is a string holding UTF-8 characters.
+//! This is a string holding UTF-8 characters. Does not allow the value more
+// than 0x10FFFF, which is not valid unicode codepoint.
class String8
{
public:
@@ -45,7 +141,8 @@
explicit String8(const String16& o);
explicit String8(const char16_t* o);
explicit String8(const char16_t* o, size_t numChars);
-
+ explicit String8(const char32_t* o);
+ explicit String8(const char32_t* o, size_t numChars);
~String8();
inline const char* string() const;
@@ -59,11 +156,20 @@
status_t setTo(const char* other);
status_t setTo(const char* other, size_t numChars);
status_t setTo(const char16_t* other, size_t numChars);
-
+ status_t setTo(const char32_t* other,
+ size_t length);
+
status_t append(const String8& other);
status_t append(const char* other);
status_t append(const char* other, size_t numChars);
+ // Note that this function takes O(N) time to calculate the value.
+ // No cache value is stored.
+ size_t getUtf32Length() const;
+ int32_t getUtf32At(size_t index,
+ size_t *next_index) const;
+ size_t getUtf32(char32_t* dst, size_t dst_len) const;
+
inline String8& operator=(const String8& other);
inline String8& operator=(const char* other);
@@ -103,7 +209,7 @@
void toLower(size_t start, size_t numChars);
void toUpper();
void toUpper(size_t start, size_t numChars);
-
+
/*
* These methods operate on the string as if it were a path name.
*/
@@ -346,7 +452,7 @@
return mString;
}
-}; // namespace android
+} // namespace android
// ---------------------------------------------------------------------------
diff --git a/libs/ui/Camera.cpp b/libs/ui/Camera.cpp
index 975594f..5015379 100644
--- a/libs/ui/Camera.cpp
+++ b/libs/ui/Camera.cpp
@@ -310,6 +310,19 @@
}
}
+// callback from camera service when timestamped frame is ready
+void Camera::dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr)
+{
+ sp<CameraListener> listener;
+ {
+ Mutex::Autolock _l(mLock);
+ listener = mListener;
+ }
+ if (listener != NULL) {
+ listener->postDataTimestamp(timestamp, msgType, dataPtr);
+ }
+}
+
void Camera::binderDied(const wp<IBinder>& who) {
LOGW("ICamera died");
notifyCallback(CAMERA_MSG_ERROR, CAMERA_ERROR_SERVER_DIED, 0);
diff --git a/libs/ui/ICameraClient.cpp b/libs/ui/ICameraClient.cpp
index c6cf75c..59a6cf2 100644
--- a/libs/ui/ICameraClient.cpp
+++ b/libs/ui/ICameraClient.cpp
@@ -27,6 +27,7 @@
enum {
NOTIFY_CALLBACK = IBinder::FIRST_CALL_TRANSACTION,
DATA_CALLBACK,
+ DATA_CALLBACK_TIMESTAMP,
};
class BpCameraClient: public BpInterface<ICameraClient>
@@ -60,6 +61,17 @@
remote()->transact(DATA_CALLBACK, data, &reply, IBinder::FLAG_ONEWAY);
}
+ // generic data callback from camera service to app with image data
+ void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& imageData)
+ {
+ LOGV("dataCallback");
+ Parcel data, reply;
+ data.writeInterfaceToken(ICameraClient::getInterfaceDescriptor());
+ data.writeInt64(timestamp);
+ data.writeInt32(msgType);
+ data.writeStrongBinder(imageData->asBinder());
+ remote()->transact(DATA_CALLBACK_TIMESTAMP, data, &reply, IBinder::FLAG_ONEWAY);
+ }
};
IMPLEMENT_META_INTERFACE(CameraClient, "android.hardware.ICameraClient");
@@ -86,13 +98,22 @@
return NO_ERROR;
} break;
case DATA_CALLBACK: {
- LOGV("RAW_CALLBACK");
+ LOGV("DATA_CALLBACK");
CHECK_INTERFACE(ICameraClient, data, reply);
int32_t msgType = data.readInt32();
sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder());
dataCallback(msgType, imageData);
return NO_ERROR;
} break;
+ case DATA_CALLBACK_TIMESTAMP: {
+ LOGV("DATA_CALLBACK_TIMESTAMP");
+ CHECK_INTERFACE(ICameraClient, data, reply);
+ nsecs_t timestamp = data.readInt64();
+ int32_t msgType = data.readInt32();
+ sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder());
+ dataCallbackTimestamp(timestamp, msgType, imageData);
+ return NO_ERROR;
+ } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/utils/String8.cpp b/libs/utils/String8.cpp
index c50d343..e908ec1 100644
--- a/libs/utils/String8.cpp
+++ b/libs/utils/String8.cpp
@@ -25,25 +25,39 @@
#include <ctype.h>
-namespace android {
+/*
+ * Functions outside android is below the namespace android, since they use
+ * functions and constants in android namespace.
+ */
// ---------------------------------------------------------------------------
-static const uint32_t kByteMask = 0x000000BF;
-static const uint32_t kByteMark = 0x00000080;
+namespace android {
+
+static const char32_t kByteMask = 0x000000BF;
+static const char32_t kByteMark = 0x00000080;
// Surrogates aren't valid for UTF-32 characters, so define some
// constants that will let us screen them out.
-static const uint32_t kUnicodeSurrogateHighStart = 0x0000D800;
-static const uint32_t kUnicodeSurrogateHighEnd = 0x0000DBFF;
-static const uint32_t kUnicodeSurrogateLowStart = 0x0000DC00;
-static const uint32_t kUnicodeSurrogateLowEnd = 0x0000DFFF;
-static const uint32_t kUnicodeSurrogateStart = kUnicodeSurrogateHighStart;
-static const uint32_t kUnicodeSurrogateEnd = kUnicodeSurrogateLowEnd;
+static const char32_t kUnicodeSurrogateHighStart = 0x0000D800;
+static const char32_t kUnicodeSurrogateHighEnd = 0x0000DBFF;
+static const char32_t kUnicodeSurrogateLowStart = 0x0000DC00;
+static const char32_t kUnicodeSurrogateLowEnd = 0x0000DFFF;
+static const char32_t kUnicodeSurrogateStart = kUnicodeSurrogateHighStart;
+static const char32_t kUnicodeSurrogateEnd = kUnicodeSurrogateLowEnd;
+static const char32_t kUnicodeMaxCodepoint = 0x0010FFFF;
// Mask used to set appropriate bits in first byte of UTF-8 sequence,
// indexed by number of bytes in the sequence.
-static const uint32_t kFirstByteMark[] = {
+// 0xxxxxxx
+// -> (00-7f) 7bit. Bit mask for the first byte is 0x00000000
+// 110yyyyx 10xxxxxx
+// -> (c0-df)(80-bf) 11bit. Bit mask is 0x000000C0
+// 1110yyyy 10yxxxxx 10xxxxxx
+// -> (e0-ef)(80-bf)(80-bf) 16bit. Bit mask is 0x000000E0
+// 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx
+// -> (f0-f7)(80-bf)(80-bf)(80-bf) 21bit. Bit mask is 0x000000F0
+static const char32_t kFirstByteMark[] = {
0x00000000, 0x00000000, 0x000000C0, 0x000000E0, 0x000000F0
};
@@ -52,7 +66,7 @@
#define RES_PATH_SEPARATOR '/'
// Return number of utf8 bytes required for the character.
-static size_t utf32_to_utf8_bytes(uint32_t srcChar)
+static size_t utf32_to_utf8_bytes(char32_t srcChar)
{
size_t bytesToWrite;
@@ -79,7 +93,7 @@
}
}
// Max code point for Unicode is 0x0010FFFF.
- else if (srcChar < 0x00110000)
+ else if (srcChar <= kUnicodeMaxCodepoint)
{
bytesToWrite = 4;
}
@@ -94,7 +108,7 @@
// Write out the source character to <dstP>.
-static void utf32_to_utf8(uint8_t* dstP, uint32_t srcChar, size_t bytes)
+static void utf32_to_utf8(uint8_t* dstP, char32_t srcChar, size_t bytes)
{
dstP += bytes;
switch (bytes)
@@ -126,7 +140,7 @@
// Bite me, Darwin!
gDarwinIsReallyAnnoying = gDarwinCantLoadAllObjects;
#endif
-
+
SharedBuffer* buf = SharedBuffer::alloc(1);
char* str = (char*)buf->data();
*str = 0;
@@ -160,20 +174,20 @@
return getEmptyString();
}
-// Note: not dealing with expanding surrogate pairs.
-static char* allocFromUTF16(const char16_t* in, size_t len)
+template<typename T, typename L>
+static char* allocFromUTF16OrUTF32(const T* in, L len)
{
if (len == 0) return getEmptyString();
-
+
size_t bytes = 0;
- const char16_t* end = in+len;
- const char16_t* p = in;
-
+ const T* end = in+len;
+ const T* p = in;
+
while (p < end) {
bytes += utf32_to_utf8_bytes(*p);
p++;
}
-
+
SharedBuffer* buf = SharedBuffer::alloc(bytes+1);
LOG_ASSERT(buf, "Unable to allocate shared buffer");
if (buf) {
@@ -181,19 +195,30 @@
char* str = (char*)buf->data();
char* d = str;
while (p < end) {
- uint32_t c = *p++;
+ const T c = *p++;
size_t len = utf32_to_utf8_bytes(c);
utf32_to_utf8((uint8_t*)d, c, len);
d += len;
}
*d = 0;
-
+
return str;
}
-
+
return getEmptyString();
}
+// Note: not dealing with expanding surrogate pairs.
+static char* allocFromUTF16(const char16_t* in, size_t len)
+{
+ return allocFromUTF16OrUTF32<char16_t, size_t>(in, len);
+}
+
+static char* allocFromUTF32(const char32_t* in, size_t len)
+{
+ return allocFromUTF16OrUTF32<char32_t, size_t>(in, len);
+}
+
// ---------------------------------------------------------------------------
String8::String8()
@@ -238,6 +263,16 @@
{
}
+String8::String8(const char32_t* o)
+ : mString(allocFromUTF32(o, strlen32(o)))
+{
+}
+
+String8::String8(const char32_t* o, size_t len)
+ : mString(allocFromUTF32(o, len))
+{
+}
+
String8::~String8()
{
SharedBuffer::bufferFromData(mString)->release();
@@ -280,6 +315,16 @@
return NO_MEMORY;
}
+status_t String8::setTo(const char32_t* other, size_t len)
+{
+ SharedBuffer::bufferFromData(mString)->release();
+ mString = allocFromUTF32(other, len);
+ if (mString) return NO_ERROR;
+
+ mString = getEmptyString();
+ return NO_MEMORY;
+}
+
status_t String8::append(const String8& other)
{
const size_t otherLen = other.bytes();
@@ -418,6 +463,21 @@
unlockBuffer(len);
}
+size_t String8::getUtf32Length() const
+{
+ return utf32_length(mString, length());
+}
+
+int32_t String8::getUtf32At(size_t index, size_t *next_index) const
+{
+ return utf32_at(mString, length(), index, next_index);
+}
+
+size_t String8::getUtf32(char32_t* dst, size_t dst_len) const
+{
+ return utf8_to_utf32(mString, length(), dst, dst_len);
+}
+
TextOutput& operator<<(TextOutput& to, const String8& val)
{
to << val.string();
@@ -427,7 +487,6 @@
// ---------------------------------------------------------------------------
// Path functions
-
void String8::setPathName(const char* name)
{
setPathName(name, strlen(name));
@@ -600,5 +659,192 @@
return *this;
}
-
}; // namespace android
+
+// ---------------------------------------------------------------------------
+
+size_t strlen32(const char32_t *s)
+{
+ const char32_t *ss = s;
+ while ( *ss )
+ ss++;
+ return ss-s;
+}
+
+size_t strnlen32(const char32_t *s, size_t maxlen)
+{
+ const char32_t *ss = s;
+ while ((maxlen > 0) && *ss) {
+ ss++;
+ maxlen--;
+ }
+ return ss-s;
+}
+
+size_t utf8_length(const char *src)
+{
+ const char *cur = src;
+ size_t ret = 0;
+ while (*cur != '\0') {
+ const char first_char = *cur++;
+ if ((first_char & 0x80) == 0) { // ASCII
+ ret += 1;
+ continue;
+ }
+ // (UTF-8's character must not be like 10xxxxxx,
+ // but 110xxxxx, 1110xxxx, ... or 1111110x)
+ if ((first_char & 0x40) == 0) {
+ return 0;
+ }
+
+ int32_t mask, to_ignore_mask;
+ size_t num_to_read = 0;
+ char32_t utf32 = 0;
+ for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80;
+ num_to_read < 5 && (first_char & mask);
+ num_to_read++, to_ignore_mask |= mask, mask >>= 1) {
+ if ((*cur & 0xC0) != 0x80) { // must be 10xxxxxx
+ return 0;
+ }
+ // 0x3F == 00111111
+ utf32 = (utf32 << 6) + (*cur++ & 0x3F);
+ }
+ // "first_char" must be (110xxxxx - 11110xxx)
+ if (num_to_read == 5) {
+ return 0;
+ }
+ to_ignore_mask |= mask;
+ utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1));
+ if (utf32 > android::kUnicodeMaxCodepoint) {
+ return 0;
+ }
+
+ ret += num_to_read;
+ }
+ return ret;
+}
+
+size_t utf32_length(const char *src, size_t src_len)
+{
+ if (src == NULL || src_len == 0) {
+ return 0;
+ }
+ size_t ret = 0;
+ const char* cur;
+ const char* end;
+ size_t num_to_skip;
+ for (cur = src, end = src + src_len, num_to_skip = 1;
+ cur < end;
+ cur += num_to_skip, ret++) {
+ const char first_char = *cur;
+ num_to_skip = 1;
+ if ((first_char & 0x80) == 0) { // ASCII
+ continue;
+ }
+ int32_t mask;
+
+ for (mask = 0x40; (first_char & mask); num_to_skip++, mask >>= 1) {
+ }
+ }
+ return ret;
+}
+
+size_t utf8_length_from_utf32(const char32_t *src, size_t src_len)
+{
+ if (src == NULL || src_len == 0) {
+ return 0;
+ }
+ size_t ret = 0;
+ const char32_t *end = src + src_len;
+ while (src < end) {
+ ret += android::utf32_to_utf8_bytes(*src++);
+ }
+ return ret;
+}
+
+static int32_t utf32_at_internal(const char* cur, size_t *num_read)
+{
+ const char first_char = *cur;
+ if ((first_char & 0x80) == 0) { // ASCII
+ *num_read = 1;
+ return *cur;
+ }
+ cur++;
+ char32_t mask, to_ignore_mask;
+ size_t num_to_read = 0;
+ char32_t utf32 = first_char;
+ for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0xFFFFFF80;
+ (first_char & mask);
+ num_to_read++, to_ignore_mask |= mask, mask >>= 1) {
+ // 0x3F == 00111111
+ utf32 = (utf32 << 6) + (*cur++ & 0x3F);
+ }
+ to_ignore_mask |= mask;
+ utf32 &= ~(to_ignore_mask << (6 * (num_to_read - 1)));
+
+ *num_read = num_to_read;
+ return static_cast<int32_t>(utf32);
+}
+
+int32_t utf32_at(const char *src, size_t src_len,
+ size_t index, size_t *next_index)
+{
+ if (index >= src_len) {
+ return -1;
+ }
+ size_t dummy_index;
+ if (next_index == NULL) {
+ next_index = &dummy_index;
+ }
+ size_t num_read;
+ int32_t ret = utf32_at_internal(src + index, &num_read);
+ if (ret >= 0) {
+ *next_index = index + num_read;
+ }
+
+ return ret;
+}
+
+size_t utf8_to_utf32(const char* src, size_t src_len,
+ char32_t* dst, size_t dst_len)
+{
+ if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) {
+ return 0;
+ }
+
+ const char* cur = src;
+ const char* end = src + src_len;
+ char32_t* cur_utf32 = dst;
+ const char32_t* end_utf32 = dst + dst_len;
+ while (cur_utf32 < end_utf32 && cur < end) {
+ size_t num_read;
+ *cur_utf32++ =
+ static_cast<char32_t>(utf32_at_internal(cur, &num_read));
+ cur += num_read;
+ }
+ if (cur_utf32 < end_utf32) {
+ *cur_utf32 = 0;
+ }
+ return static_cast<size_t>(cur_utf32 - dst);
+}
+
+size_t utf32_to_utf8(const char32_t* src, size_t src_len,
+ char* dst, size_t dst_len)
+{
+ if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) {
+ return 0;
+ }
+ const char32_t *cur_utf32 = src;
+ const char32_t *end_utf32 = src + src_len;
+ char *cur = dst;
+ const char *end = dst + dst_len;
+ while (cur_utf32 < end_utf32 && cur < end) {
+ size_t len = android::utf32_to_utf8_bytes(*cur_utf32);
+ android::utf32_to_utf8((uint8_t *)cur, *cur_utf32++, len);
+ cur += len;
+ }
+ if (cur < end) {
+ *cur = '\0';
+ }
+ return cur - dst;
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index cfdf5e3..a65a417 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1063,6 +1063,21 @@
}
}
+ /**
+ * @hide
+ * Reload audio settings. This method is called by Settings backup
+ * agent when audio settings are restored and causes the AudioService
+ * to read and apply restored settings.
+ */
+ public void reloadAudioSettings() {
+ IAudioService service = getService();
+ try {
+ service.reloadAudioSettings();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in reloadAudioSettings"+e);
+ }
+ }
+
/**
* {@hide}
*/
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 937baad..ee41021 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -508,14 +508,14 @@
/** @see AudioManager#setRingerMode(int) */
public void setRingerMode(int ringerMode) {
if (ringerMode != mRingerMode) {
- setRingerModeInt(ringerMode);
+ setRingerModeInt(ringerMode, true);
// Send sticky broadcast
broadcastRingerMode();
}
}
- private void setRingerModeInt(int ringerMode) {
+ private void setRingerModeInt(int ringerMode, boolean persist) {
mRingerMode = ringerMode;
// Adjust volumes via posting message
@@ -543,8 +543,10 @@
}
// Post a persist ringer mode msg
- sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE, SHARED_MSG,
- SENDMSG_REPLACE, 0, 0, null, PERSIST_DELAY);
+ if (persist) {
+ sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE, SHARED_MSG,
+ SENDMSG_REPLACE, 0, 0, null, PERSIST_DELAY);
+ }
}
/** @see AudioManager#shouldVibrate(int) */
@@ -914,6 +916,46 @@
}
}
+ /** @see AudioManager#reloadAudioSettings() */
+ public void reloadAudioSettings() {
+ // restore ringer mode, ringer mode affected streams, mute affected streams and vibrate settings
+ readPersistedSettings();
+
+ // restore volume settings
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = 0; streamType < numStreamTypes; streamType++) {
+ VolumeStreamState streamState = mStreamStates[streamType];
+
+ // there is no volume setting for STREAM_BLUETOOTH_SCO
+ if (streamType != AudioSystem.STREAM_BLUETOOTH_SCO) {
+ String settingName = System.VOLUME_SETTINGS[streamType];
+ String lastAudibleSettingName = settingName + System.APPEND_FOR_LAST_AUDIBLE;
+
+ streamState.mIndex = streamState.getValidIndex(Settings.System.getInt(mContentResolver,
+ settingName,
+ AudioManager.DEFAULT_STREAM_VOLUME[streamType]));
+ streamState.mLastAudibleIndex = streamState.getValidIndex(Settings.System.getInt(mContentResolver,
+ lastAudibleSettingName,
+ streamState.mIndex > 0 ? streamState.mIndex : AudioManager.DEFAULT_STREAM_VOLUME[streamType]));
+ }
+ // unmute stream that whas muted but is not affect by mute anymore
+ if (streamState.muteCount() != 0 && !isStreamAffectedByMute(streamType)) {
+ int size = streamState.mDeathHandlers.size();
+ for (int i = 0; i < size; i++) {
+ streamState.mDeathHandlers.get(i).mMuteCount = 1;
+ streamState.mDeathHandlers.get(i).mute(false);
+ }
+ }
+ // apply stream volume
+ if (streamState.muteCount() == 0) {
+ AudioSystem.setVolume(streamType, streamState.mVolumes[streamState.mIndex]);
+ }
+ }
+
+ // apply new ringer mode
+ setRingerModeInt(getRingerMode(), false);
+ }
+
///////////////////////////////////////////////////////////////////////////
// Internal methods
///////////////////////////////////////////////////////////////////////////
@@ -1426,7 +1468,7 @@
* Ensure all stream types that should be affected by ringer mode
* are in the proper state.
*/
- setRingerModeInt(getRingerMode());
+ setRingerModeInt(getRingerMode(), false);
}
}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
new file mode 100644
index 0000000..645f3f6
--- /dev/null
+++ b/media/java/android/media/ExifInterface.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Wrapper for native Exif library
+ * {@hide}
+ */
+public class ExifInterface {
+ private static final String TAG = "ExifInterface";
+ private String mFilename;
+
+ // Constants used for the Orientation Exif tag.
+ public static final int ORIENTATION_UNDEFINED = 0;
+ public static final int ORIENTATION_NORMAL = 1;
+
+ // Constants used for white balance
+ public static final int WHITEBALANCE_AUTO = 0;
+ public static final int WHITEBALANCE_MANUAL = 1;
+
+ // left right reversed mirror
+ public static final int ORIENTATION_FLIP_HORIZONTAL = 2;
+ public static final int ORIENTATION_ROTATE_180 = 3;
+
+ // upside down mirror
+ public static final int ORIENTATION_FLIP_VERTICAL = 4;
+
+ // flipped about top-left <--> bottom-right axis
+ public static final int ORIENTATION_TRANSPOSE = 5;
+
+ // rotate 90 cw to right it
+ public static final int ORIENTATION_ROTATE_90 = 6;
+
+ // flipped about top-right <--> bottom-left axis
+ public static final int ORIENTATION_TRANSVERSE = 7;
+
+ // rotate 270 to right it
+ public static final int ORIENTATION_ROTATE_270 = 8;
+
+ // The Exif tag names
+ public static final String TAG_ORIENTATION = "Orientation";
+
+ public static final String TAG_DATE_TIME_ORIGINAL = "DateTimeOriginal";
+ public static final String TAG_MAKE = "Make";
+ public static final String TAG_MODEL = "Model";
+ public static final String TAG_FLASH = "Flash";
+ public static final String TAG_IMAGE_WIDTH = "ImageWidth";
+ public static final String TAG_IMAGE_LENGTH = "ImageLength";
+
+ public static final String TAG_GPS_LATITUDE = "GPSLatitude";
+ public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
+
+ public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
+ public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
+ public static final String TAG_WHITE_BALANCE = "WhiteBalance";
+
+ private boolean mSavedAttributes = false;
+ private boolean mHasThumbnail = false;
+ private HashMap<String, String> mCachedAttributes = null;
+
+ static {
+ System.loadLibrary("exif");
+ }
+
+ private static ExifInterface sExifObj = null;
+ /**
+ * Since the underlying jhead native code is not thread-safe,
+ * ExifInterface should use singleton interface instead of public
+ * constructor.
+ */
+ private static synchronized ExifInterface instance() {
+ if (sExifObj == null) {
+ sExifObj = new ExifInterface();
+ }
+
+ return sExifObj;
+ }
+
+ /**
+ * The following 3 static methods are handy routines for atomic operation
+ * of underlying jhead library. It retrieves EXIF data and then release
+ * ExifInterface immediately.
+ */
+ public static synchronized HashMap<String, String> loadExifData(String filename) {
+ ExifInterface exif = instance();
+ HashMap<String, String> exifData = null;
+ if (exif != null) {
+ exif.setFilename(filename);
+ exifData = exif.getAttributes();
+ }
+ return exifData;
+ }
+
+ public static synchronized void saveExifData(String filename, HashMap<String, String> exifData) {
+ ExifInterface exif = instance();
+ if (exif != null) {
+ exif.setFilename(filename);
+ exif.saveAttributes(exifData);
+ }
+ }
+
+ public static synchronized byte[] getExifThumbnail(String filename) {
+ ExifInterface exif = instance();
+ if (exif != null) {
+ exif.setFilename(filename);
+ return exif.getThumbnail();
+ }
+ return null;
+ }
+
+ public void setFilename(String filename) {
+ mFilename = filename;
+ }
+
+ /**
+ * Given a HashMap of Exif tags and associated values, an Exif section in
+ * the JPG file is created and loaded with the tag data. saveAttributes()
+ * is expensive because it involves copying all the JPG data from one file
+ * to another and deleting the old file and renaming the other. It's best
+ * to collect all the attributes to write and make a single call rather
+ * than multiple calls for each attribute. You must call "commitChanges()"
+ * at some point to commit the changes.
+ */
+ public void saveAttributes(HashMap<String, String> attributes) {
+ // format of string passed to native C code:
+ // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
+ // example:
+ // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
+ StringBuilder sb = new StringBuilder();
+ int size = attributes.size();
+ if (attributes.containsKey("hasThumbnail")) {
+ --size;
+ }
+ sb.append(size + " ");
+ for (Map.Entry<String, String> iter : attributes.entrySet()) {
+ String key = iter.getKey();
+ if (key.equals("hasThumbnail")) {
+ // this is a fake attribute not saved as an exif tag
+ continue;
+ }
+ String val = iter.getValue();
+ sb.append(key + "=");
+ sb.append(val.length() + " ");
+ sb.append(val);
+ }
+ String s = sb.toString();
+ saveAttributesNative(mFilename, s);
+ commitChangesNative(mFilename);
+ mSavedAttributes = true;
+ }
+
+ /**
+ * Returns a HashMap loaded with the Exif attributes of the file. The key
+ * is the standard tag name and the value is the tag's value: e.g.
+ * Model -> Nikon. Numeric values are returned as strings.
+ */
+ public HashMap<String, String> getAttributes() {
+ if (mCachedAttributes != null) {
+ return mCachedAttributes;
+ }
+ // format of string passed from native C code:
+ // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
+ // example:
+ // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
+ mCachedAttributes = new HashMap<String, String>();
+
+ String attrStr = getAttributesNative(mFilename);
+
+ // get count
+ int ptr = attrStr.indexOf(' ');
+ int count = Integer.parseInt(attrStr.substring(0, ptr));
+ // skip past the space between item count and the rest of the attributes
+ ++ptr;
+
+ for (int i = 0; i < count; i++) {
+ // extract the attribute name
+ int equalPos = attrStr.indexOf('=', ptr);
+ String attrName = attrStr.substring(ptr, equalPos);
+ ptr = equalPos + 1; // skip past =
+
+ // extract the attribute value length
+ int lenPos = attrStr.indexOf(' ', ptr);
+ int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
+ ptr = lenPos + 1; // skip pas the space
+
+ // extract the attribute value
+ String attrValue = attrStr.substring(ptr, ptr + attrLen);
+ ptr += attrLen;
+
+ if (attrName.equals("hasThumbnail")) {
+ mHasThumbnail = attrValue.equalsIgnoreCase("true");
+ } else {
+ mCachedAttributes.put(attrName, attrValue);
+ }
+ }
+ return mCachedAttributes;
+ }
+
+ /**
+ * Given a numerical white balance value, return a
+ * human-readable string describing it.
+ */
+ public static String whiteBalanceToString(int whitebalance) {
+ switch (whitebalance) {
+ case WHITEBALANCE_AUTO:
+ return "Auto";
+ case WHITEBALANCE_MANUAL:
+ return "Manual";
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * Given a numerical orientation, return a human-readable string describing
+ * the orientation.
+ */
+ public static String orientationToString(int orientation) {
+ // TODO: this function needs to be localized and use string resource ids
+ // rather than strings
+ String orientationString;
+ switch (orientation) {
+ case ORIENTATION_NORMAL:
+ orientationString = "Normal";
+ break;
+ case ORIENTATION_FLIP_HORIZONTAL:
+ orientationString = "Flipped horizontal";
+ break;
+ case ORIENTATION_ROTATE_180:
+ orientationString = "Rotated 180 degrees";
+ break;
+ case ORIENTATION_FLIP_VERTICAL:
+ orientationString = "Upside down mirror";
+ break;
+ case ORIENTATION_TRANSPOSE:
+ orientationString = "Transposed";
+ break;
+ case ORIENTATION_ROTATE_90:
+ orientationString = "Rotated 90 degrees";
+ break;
+ case ORIENTATION_TRANSVERSE:
+ orientationString = "Transversed";
+ break;
+ case ORIENTATION_ROTATE_270:
+ orientationString = "Rotated 270 degrees";
+ break;
+ default:
+ orientationString = "Undefined";
+ break;
+ }
+ return orientationString;
+ }
+
+ /**
+ * Copies the thumbnail data out of the filename and puts it in the Exif
+ * data associated with the file used to create this object. You must call
+ * "commitChanges()" at some point to commit the changes.
+ */
+ public boolean appendThumbnail(String thumbnailFileName) {
+ if (!mSavedAttributes) {
+ throw new RuntimeException("Must call saveAttributes "
+ + "before calling appendThumbnail");
+ }
+ mHasThumbnail = appendThumbnailNative(mFilename, thumbnailFileName);
+ return mHasThumbnail;
+ }
+
+ public boolean hasThumbnail() {
+ if (!mSavedAttributes) {
+ getAttributes();
+ }
+ return mHasThumbnail;
+ }
+
+ public byte[] getThumbnail() {
+ return getThumbnailNative(mFilename);
+ }
+
+ public static float[] getLatLng(HashMap<String, String> exifData) {
+ if (exifData == null) {
+ return null;
+ }
+
+ String latValue = exifData.get(ExifInterface.TAG_GPS_LATITUDE);
+ String latRef = exifData.get(ExifInterface.TAG_GPS_LATITUDE_REF);
+ String lngValue = exifData.get(ExifInterface.TAG_GPS_LONGITUDE);
+ String lngRef = exifData.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
+ float[] latlng = null;
+
+ if (latValue != null && latRef != null
+ && lngValue != null && lngRef != null) {
+ latlng = new float[2];
+ latlng[0] = ExifInterface.convertRationalLatLonToFloat(
+ latValue, latRef);
+ latlng[1] = ExifInterface.convertRationalLatLonToFloat(
+ lngValue, lngRef);
+ }
+
+ return latlng;
+ }
+
+ public static float convertRationalLatLonToFloat(
+ String rationalString, String ref) {
+ try {
+ String [] parts = rationalString.split(",");
+
+ String [] pair;
+ pair = parts[0].split("/");
+ int degrees = (int) (Float.parseFloat(pair[0].trim())
+ / Float.parseFloat(pair[1].trim()));
+
+ pair = parts[1].split("/");
+ int minutes = (int) ((Float.parseFloat(pair[0].trim())
+ / Float.parseFloat(pair[1].trim())));
+
+ pair = parts[2].split("/");
+ float seconds = Float.parseFloat(pair[0].trim())
+ / Float.parseFloat(pair[1].trim());
+
+ float result = degrees + (minutes / 60F) + (seconds / (60F * 60F));
+ if ((ref.equals("S") || ref.equals("W"))) {
+ return -result;
+ }
+ return result;
+ } catch (RuntimeException ex) {
+ // if for whatever reason we can't parse the lat long then return
+ // null
+ return 0f;
+ }
+ }
+
+ public static String convertRationalLatLonToDecimalString(
+ String rationalString, String ref, boolean usePositiveNegative) {
+ float result = convertRationalLatLonToFloat(rationalString, ref);
+
+ String preliminaryResult = String.valueOf(result);
+ if (usePositiveNegative) {
+ String neg = (ref.equals("S") || ref.equals("E")) ? "-" : "";
+ return neg + preliminaryResult;
+ } else {
+ return preliminaryResult + String.valueOf((char) 186) + " "
+ + ref;
+ }
+ }
+
+ public static String makeLatLongString(double d) {
+ d = Math.abs(d);
+
+ int degrees = (int) d;
+
+ double remainder = d - degrees;
+ int minutes = (int) (remainder * 60D);
+ // really seconds * 1000
+ int seconds = (int) (((remainder * 60D) - minutes) * 60D * 1000D);
+
+ String retVal = degrees + "/1," + minutes + "/1," + seconds + "/1000";
+ return retVal;
+ }
+
+ public static String makeLatStringRef(double lat) {
+ return lat >= 0D ? "N" : "S";
+ }
+
+ public static String makeLonStringRef(double lon) {
+ return lon >= 0D ? "W" : "E";
+ }
+
+ private native boolean appendThumbnailNative(String fileName,
+ String thumbnailFileName);
+
+ private native void saveAttributesNative(String fileName,
+ String compressedAttributes);
+
+ private native String getAttributesNative(String fileName);
+
+ private native void commitChangesNative(String fileName);
+
+ private native byte[] getThumbnailNative(String fileName);
+}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index f5e242d..9a8264f 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -71,4 +71,5 @@
oneway void unloadSoundEffects();
+ oneway void reloadAudioSettings();
}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index cccc0fc..6de7bc1 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -54,7 +54,7 @@
/**
* Internal service helper that no-one should use directly.
- *
+ *
* The way the scan currently works is:
* - The Java MediaScannerService creates a MediaScanner (this class), and calls
* MediaScanner.scanDirectories on it.
@@ -96,7 +96,7 @@
* {@hide}
*/
public class MediaScanner
-{
+{
static {
System.loadLibrary("media_jni");
}
@@ -108,17 +108,17 @@
Audio.Media.DATA, // 1
Audio.Media.DATE_MODIFIED, // 2
};
-
+
private static final int ID_AUDIO_COLUMN_INDEX = 0;
private static final int PATH_AUDIO_COLUMN_INDEX = 1;
private static final int DATE_MODIFIED_AUDIO_COLUMN_INDEX = 2;
-
+
private static final String[] VIDEO_PROJECTION = new String[] {
Video.Media._ID, // 0
Video.Media.DATA, // 1
Video.Media.DATE_MODIFIED, // 2
};
-
+
private static final int ID_VIDEO_COLUMN_INDEX = 0;
private static final int PATH_VIDEO_COLUMN_INDEX = 1;
private static final int DATE_MODIFIED_VIDEO_COLUMN_INDEX = 2;
@@ -128,11 +128,11 @@
Images.Media.DATA, // 1
Images.Media.DATE_MODIFIED, // 2
};
-
+
private static final int ID_IMAGES_COLUMN_INDEX = 0;
private static final int PATH_IMAGES_COLUMN_INDEX = 1;
private static final int DATE_MODIFIED_IMAGES_COLUMN_INDEX = 2;
-
+
private static final String[] PLAYLISTS_PROJECTION = new String[] {
Audio.Playlists._ID, // 0
Audio.Playlists.DATA, // 1
@@ -157,7 +157,7 @@
private static final String ALARMS_DIR = "/alarms/";
private static final String MUSIC_DIR = "/music/";
private static final String PODCAST_DIR = "/podcasts/";
-
+
private static final String[] ID3_GENRES = {
// ID3v1 Genres
"Blues",
@@ -317,11 +317,11 @@
* to get the full system property.
*/
private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
-
+
// set to true if file path comparisons should be case insensitive.
// this should be set when scanning files on a case insensitive file system.
private boolean mCaseInsensitivePaths;
-
+
private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
private static class FileCacheEntry {
@@ -331,7 +331,7 @@
long mLastModified;
boolean mSeenInFileSystem;
boolean mLastModifiedChanged;
-
+
FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified) {
mTableUri = tableUri;
mRowId = rowId;
@@ -346,10 +346,10 @@
return mPath;
}
}
-
- // hashes file path to FileCacheEntry.
+
+ // hashes file path to FileCacheEntry.
// path should be lower case if mCaseInsensitivePaths is true
- private HashMap<String, FileCacheEntry> mFileCache;
+ private HashMap<String, FileCacheEntry> mFileCache;
private ArrayList<FileCacheEntry> mPlayLists;
private HashMap<String, Uri> mGenreCache;
@@ -360,7 +360,7 @@
mContext = c;
mBitmapOptions.inSampleSize = 1;
mBitmapOptions.inJustDecodeBounds = true;
-
+
setDefaultRingtoneFileNames();
}
@@ -370,11 +370,11 @@
mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
+ Settings.System.NOTIFICATION_SOUND);
}
-
+
private MyMediaScannerClient mClient = new MyMediaScannerClient();
-
+
private class MyMediaScannerClient implements MediaScannerClient {
-
+
private String mArtist;
private String mAlbumArtist; // use this if mArtist is missing
private String mAlbum;
@@ -389,11 +389,11 @@
private String mPath;
private long mLastModified;
private long mFileSize;
-
+
public FileCacheEntry beginFile(String path, String mimeType, long lastModified, long fileSize) {
-
+
// special case certain file names
- // I use regionMatches() instead of substring() below
+ // I use regionMatches() instead of substring() below
// to avoid memory allocation
int lastSlash = path.lastIndexOf('/');
if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
@@ -401,7 +401,7 @@
if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
return null;
}
-
+
// ignore album art files created by Windows Media Player:
// Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg and AlbumArt_{...}_Small.jpg
if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
@@ -416,7 +416,7 @@
}
}
}
-
+
mMimeType = null;
// try mimeType first, if it is specified
if (mimeType != null) {
@@ -435,7 +435,7 @@
mMimeType = mediaFileType.mimeType;
}
}
-
+
String key = path;
if (mCaseInsensitivePaths) {
key = path.toLowerCase();
@@ -446,20 +446,20 @@
mFileCache.put(key, entry);
}
entry.mSeenInFileSystem = true;
-
+
// add some slack to avoid a rounding error
long delta = lastModified - entry.mLastModified;
if (delta > 1 || delta < -1) {
entry.mLastModified = lastModified;
entry.mLastModifiedChanged = true;
}
-
+
if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
mPlayLists.add(entry);
// we don't process playlists in the main scan, so return null
return null;
}
-
+
// clear all the metadata
mArtist = null;
mAlbumArtist = null;
@@ -472,10 +472,10 @@
mDuration = 0;
mPath = path;
mLastModified = lastModified;
-
+
return entry;
}
-
+
public void scanFile(String path, long lastModified, long fileSize) {
doScanFile(path, null, lastModified, fileSize, false);
}
@@ -513,7 +513,7 @@
} else if (MediaFile.isImageFileType(mFileType)) {
// we used to compute the width and height but it's not worth it
}
-
+
result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
}
} catch (RemoteException e) {
@@ -531,17 +531,17 @@
char ch = s.charAt(start++);
// return defaultValue if we have no integer at all
if (ch < '0' || ch > '9') return defaultValue;
-
+
int result = ch - '0';
while (start < length) {
ch = s.charAt(start++);
if (ch < '0' || ch > '9') return result;
result = result * 10 + (ch - '0');
}
-
+
return result;
- }
-
+ }
+
public void handleStringTag(String name, String value) {
if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {
// Don't trim() here, to preserve the special \001 character
@@ -577,7 +577,7 @@
// track number might be of the form "2/12"
// we just read the number before the slash
int num = parseSubstring(value, 0, 0);
- mTrack = (mTrack / 1000) * 1000 + num;
+ mTrack = (mTrack / 1000) * 1000 + num;
} else if (name.equalsIgnoreCase("discnumber") ||
name.equals("set") || name.startsWith("set;")) {
// set number might be of the form "1/3"
@@ -588,16 +588,16 @@
mDuration = parseSubstring(value, 0, 0);
}
}
-
+
public void setMimeType(String mimeType) {
mMimeType = mimeType;
mFileType = MediaFile.getFileTypeForMimeType(mimeType);
}
-
+
/**
* Formats the data into a values array suitable for use with the Media
* Content Provider.
- *
+ *
* @return a map of values
*/
private ContentValues toValues() {
@@ -608,7 +608,7 @@
map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
map.put(MediaStore.MediaColumns.SIZE, mFileSize);
map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
-
+
if (MediaFile.isVideoFileType(mFileType)) {
map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaFile.UNKNOWN_STRING));
map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaFile.UNKNOWN_STRING));
@@ -629,9 +629,9 @@
}
return map;
}
-
+
private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,
- boolean alarms, boolean music, boolean podcasts)
+ boolean alarms, boolean music, boolean podcasts)
throws RemoteException {
// update database
Uri tableUri;
@@ -649,7 +649,7 @@
return null;
}
entry.mTableUri = tableUri;
-
+
// use album artist if artist is missing
if (mArtist == null || mArtist.length() == 0) {
mArtist = mAlbumArtist;
@@ -680,10 +680,18 @@
values.put(Audio.Media.IS_ALARM, alarms);
values.put(Audio.Media.IS_MUSIC, music);
values.put(Audio.Media.IS_PODCAST, podcasts);
- } else if (isImage) {
- // nothing right now
+ } else if (mFileType == MediaFile.FILE_TYPE_JPEG) {
+ HashMap<String, String> exifData =
+ ExifInterface.loadExifData(entry.mPath);
+ if (exifData != null) {
+ float[] latlng = ExifInterface.getLatLng(exifData);
+ if (latlng != null) {
+ values.put(Images.Media.LATITUDE, latlng[0]);
+ values.put(Images.Media.LONGITUDE, latlng[1]);
+ }
+ }
}
-
+
Uri result = null;
long rowId = entry.mRowId;
if (rowId == 0) {
@@ -730,15 +738,15 @@
}
}
}
-
+
if (uri != null) {
- // add entry to audio_genre_map
+ // add entry to audio_genre_map
values.clear();
values.put(MediaStore.Audio.Genres.Members.AUDIO_ID, Long.valueOf(rowId));
mMediaProvider.insert(uri, values);
}
}
-
+
if (notifications && !mDefaultNotificationSet) {
if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
@@ -752,36 +760,36 @@
mDefaultRingtoneSet = true;
}
}
-
+
return result;
}
-
+
private boolean doesPathHaveFilename(String path, String filename) {
int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1;
int filenameLength = filename.length();
return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) &&
pathFilenameStart + filenameLength == path.length();
}
-
+
private void setSettingIfNotSet(String settingName, Uri uri, long rowId) {
-
+
String existingSettingValue = Settings.System.getString(mContext.getContentResolver(),
settingName);
-
+
if (TextUtils.isEmpty(existingSettingValue)) {
// Set the setting to the given URI
Settings.System.putString(mContext.getContentResolver(), settingName,
ContentUris.withAppendedId(uri, rowId).toString());
}
}
-
+
}; // end of anonymous MediaScannerClient instance
-
+
private void prescan(String filePath) throws RemoteException {
Cursor c = null;
String where = null;
String[] selectionArgs = null;
-
+
if (mFileCache == null) {
mFileCache = new HashMap<String, FileCacheEntry>();
} else {
@@ -792,7 +800,7 @@
} else {
mPlayLists.clear();
}
-
+
// Build the list of files from the content provider
try {
// Read existing files from the audio table
@@ -801,14 +809,14 @@
selectionArgs = new String[] { filePath };
}
c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where, selectionArgs, null);
-
+
if (c != null) {
try {
while (c.moveToNext()) {
long rowId = c.getLong(ID_AUDIO_COLUMN_INDEX);
String path = c.getString(PATH_AUDIO_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX);
-
+
String key = path;
if (mCaseInsensitivePaths) {
key = path.toLowerCase();
@@ -829,14 +837,14 @@
where = null;
}
c = mMediaProvider.query(mVideoUri, VIDEO_PROJECTION, where, selectionArgs, null);
-
+
if (c != null) {
try {
while (c.moveToNext()) {
long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX);
String path = c.getString(PATH_VIDEO_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX);
-
+
String key = path;
if (mCaseInsensitivePaths) {
key = path.toLowerCase();
@@ -858,7 +866,7 @@
}
mOriginalCount = 0;
c = mMediaProvider.query(mImagesUri, IMAGES_PROJECTION, where, selectionArgs, null);
-
+
if (c != null) {
try {
mOriginalCount = c.getCount();
@@ -866,7 +874,7 @@
long rowId = c.getLong(ID_IMAGES_COLUMN_INDEX);
String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX);
-
+
String key = path;
if (mCaseInsensitivePaths) {
key = path.toLowerCase();
@@ -879,7 +887,7 @@
c = null;
}
}
-
+
if (mProcessPlaylists) {
// Read existing files from the playlists table
if (filePath != null) {
@@ -888,16 +896,16 @@
where = null;
}
c = mMediaProvider.query(mPlaylistsUri, PLAYLISTS_PROJECTION, where, selectionArgs, null);
-
+
if (c != null) {
try {
while (c.moveToNext()) {
String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
-
+
if (path != null && path.length() > 0) {
long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX);
-
+
String key = path;
if (mCaseInsensitivePaths) {
key = path.toLowerCase();
@@ -919,7 +927,7 @@
}
}
}
-
+
private boolean inScanDirectory(String path, String[] directories) {
for (int i = 0; i < directories.length; i++) {
if (path.startsWith(directories[i])) {
@@ -928,25 +936,25 @@
}
return false;
}
-
+
private void pruneDeadThumbnailFiles() {
HashSet<String> existingFiles = new HashSet<String>();
String directory = "/sdcard/DCIM/.thumbnails";
String [] files = (new File(directory)).list();
if (files == null)
files = new String[0];
-
+
for (int i = 0; i < files.length; i++) {
String fullPathString = directory + "/" + files[i];
existingFiles.add(fullPathString);
}
-
+
try {
Cursor c = mMediaProvider.query(
- mThumbsUri,
- new String [] { "_data" },
- null,
- null,
+ mThumbsUri,
+ new String [] { "_data" },
+ null,
+ null,
null);
Log.v(TAG, "pruneDeadThumbnailFiles... " + c);
if (c != null && c.moveToFirst()) {
@@ -955,7 +963,7 @@
existingFiles.remove(fullPathString);
} while (c.moveToNext());
}
-
+
for (String fileToDelete : existingFiles) {
if (Config.LOGV)
Log.v(TAG, "fileToDelete is " + fileToDelete);
@@ -964,7 +972,7 @@
} catch (SecurityException ex) {
}
}
-
+
Log.v(TAG, "/pruneDeadThumbnailFiles... " + c);
if (c != null) {
c.close();
@@ -980,10 +988,10 @@
while (iterator.hasNext()) {
FileCacheEntry entry = iterator.next();
String path = entry.mPath;
-
+
// remove database entries for files that no longer exist.
boolean fileMissing = false;
-
+
if (!entry.mSeenInFileSystem) {
if (inScanDirectory(path, directories)) {
// we didn't see this file in the scan directory.
@@ -997,7 +1005,7 @@
}
}
}
-
+
if (fileMissing) {
// do not delete missing playlists, since they may have been modified by the user.
// the user can delete them in the media player instead.
@@ -1016,25 +1024,25 @@
}
}
}
-
+
// handle playlists last, after we know what media files are on the storage.
if (mProcessPlaylists) {
processPlayLists();
}
-
+
if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external")))
pruneDeadThumbnailFiles();
-
+
// allow GC to clean up
mGenreCache = null;
mPlayLists = null;
mFileCache = null;
mMediaProvider = null;
}
-
+
private void initialize(String volumeName) {
mMediaProvider = mContext.getContentResolver().acquireProvider("media");
-
+
mAudioUri = Audio.Media.getContentUri(volumeName);
mVideoUri = Video.Media.getContentUri(volumeName);
mImagesUri = Images.Media.getContentUri(volumeName);
@@ -1051,23 +1059,23 @@
if ( Process.supportsProcesses()) {
mCaseInsensitivePaths = true;
}
- }
+ }
}
public void scanDirectories(String[] directories, String volumeName) {
try {
long start = System.currentTimeMillis();
- initialize(volumeName);
+ initialize(volumeName);
prescan(null);
long prescan = System.currentTimeMillis();
-
+
for (int i = 0; i < directories.length; i++) {
processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
}
long scan = System.currentTimeMillis();
postscan(directories);
long end = System.currentTimeMillis();
-
+
if (Config.LOGD) {
Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n");
Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n");
@@ -1088,9 +1096,9 @@
// this function is used to scan a single file
public Uri scanSingleFile(String path, String volumeName, String mimeType) {
try {
- initialize(volumeName);
+ initialize(volumeName);
prescan(path);
-
+
File file = new File(path);
// always scan the file, so we can return the content://media Uri for existing files
return mClient.doScanFile(path, mimeType, file.lastModified(), file.length(), true);
@@ -1105,7 +1113,7 @@
int result = 0;
int end1 = path1.length();
int end2 = path2.length();
-
+
while (end1 > 0 && end2 > 0) {
int slash1 = path1.lastIndexOf('/', end1 - 1);
int slash2 = path2.lastIndexOf('/', end2 - 1);
@@ -1123,13 +1131,13 @@
end2 = start2 - 1;
} else break;
}
-
+
return result;
}
- private boolean addPlayListEntry(String entry, String playListDirectory,
+ private boolean addPlayListEntry(String entry, String playListDirectory,
Uri uri, ContentValues values, int index) {
-
+
// watch for trailing whitespace
int entryLength = entry.length();
while (entryLength > 0 && Character.isWhitespace(entry.charAt(entryLength - 1))) entryLength--;
@@ -1146,36 +1154,36 @@
// if we have a relative path, combine entry with playListDirectory
if (!fullPath)
entry = playListDirectory + entry;
-
+
//FIXME - should we look for "../" within the path?
-
+
// best matching MediaFile for the play list entry
FileCacheEntry bestMatch = null;
-
+
// number of rightmost file/directory names for bestMatch
- int bestMatchLength = 0;
-
+ int bestMatchLength = 0;
+
Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
while (iterator.hasNext()) {
FileCacheEntry cacheEntry = iterator.next();
String path = cacheEntry.mPath;
-
+
if (path.equalsIgnoreCase(entry)) {
bestMatch = cacheEntry;
break; // don't bother continuing search
}
-
+
int matchLength = matchPaths(path, entry);
if (matchLength > bestMatchLength) {
bestMatch = cacheEntry;
bestMatchLength = matchLength;
}
}
-
+
if (bestMatch == null) {
return false;
}
-
+
try {
// OK, now we need to add this to the database
values.clear();
@@ -1189,7 +1197,7 @@
return true;
}
-
+
private void processM3uPlayList(String path, String playListDirectory, Uri uri, ContentValues values) {
BufferedReader reader = null;
try {
@@ -1266,7 +1274,7 @@
public WplHandler(String playListDirectory, Uri uri) {
this.playListDirectory = playListDirectory;
this.uri = uri;
-
+
RootElement root = new RootElement("smil");
Element body = root.getChild("body");
Element seq = body.getChild("seq");
@@ -1316,12 +1324,12 @@
}
}
}
-
+
private void processPlayLists() throws RemoteException {
Iterator<FileCacheEntry> iterator = mPlayLists.iterator();
while (iterator.hasNext()) {
FileCacheEntry entry = iterator.next();
- String path = entry.mPath;
+ String path = entry.mPath;
// only process playlist files if they are new or have been modified since the last scan
if (entry.mLastModifiedChanged) {
@@ -1332,7 +1340,7 @@
long rowId = entry.mRowId;
if (rowId == 0) {
// Create a new playlist
-
+
int lastDot = path.lastIndexOf('.');
String name = (lastDot < 0 ? path.substring(lastSlash + 1) : path.substring(lastSlash + 1, lastDot));
values.put(MediaStore.Audio.Playlists.NAME, name);
@@ -1343,7 +1351,7 @@
membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
} else {
uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
-
+
// update lastModified value of existing playlist
values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
mMediaProvider.update(uri, values, null, null);
@@ -1352,7 +1360,7 @@
membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
mMediaProvider.delete(membersUri, null, null);
}
-
+
String playListDirectory = path.substring(0, lastSlash + 1);
MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
@@ -1363,7 +1371,7 @@
processPlsPlayList(path, playListDirectory, membersUri, values);
else if (fileType == MediaFile.FILE_TYPE_WPL)
processWplPlayList(path, playListDirectory, membersUri);
-
+
Cursor cursor = mMediaProvider.query(membersUri, PLAYLIST_MEMBERS_PROJECTION, null,
null, null);
try {
@@ -1377,18 +1385,18 @@
}
}
}
-
+
private native void processDirectory(String path, String extensions, MediaScannerClient client);
private native void processFile(String path, String mimeType, MediaScannerClient client);
public native void setLocale(String locale);
-
+
public native byte[] extractAlbumArt(FileDescriptor fd);
private native final void native_setup();
private native final void native_finalize();
@Override
- protected void finalize() {
+ protected void finalize() {
mContext.getContentResolver().releaseProvider(mMediaProvider);
- native_finalize();
+ native_finalize();
}
}
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 3b82284..6e28515 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -810,17 +810,25 @@
// Now propagate the newly-backed-up data to the transport
if (success) {
- if (DEBUG) Log.v(TAG, "doBackup() success; calling transport");
- backupData =
- ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY);
- if (!transport.performBackup(packInfo, backupData)) {
- // STOPSHIP TODO: handle errors
- Log.e(TAG, "Backup failure in performBackup()");
+ if (DEBUG) Log.v(TAG, "doBackup() success");
+ if (backupDataName.length() > 0) {
+ backupData =
+ ParcelFileDescriptor.open(backupDataName,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ if (!transport.performBackup(packInfo, backupData)) {
+ // STOPSHIP TODO: handle errors
+ Log.e(TAG, "Backup failure in performBackup()");
+ }
+ } else {
+ if (DEBUG) {
+ Log.i(TAG, "no backup data written; not calling transport");
+ }
}
- // !!! TODO: After successful transport, delete the now-stale data
- // and juggle the files so that next time the new state is passed
- //backupDataName.delete();
+ // After successful transport, delete the now-stale data
+ // and juggle the files so that next time we supply the agent
+ // with the new state file it just created.
+ backupDataName.delete();
newStateName.renameTo(savedStateName);
}
} catch (Exception e) {
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 854138c..190d3e6 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -88,7 +88,8 @@
private NotificationRecord mSoundNotification;
private AsyncPlayer mSound;
- private int mDisabledNotifications;
+ private boolean mSystemReady;
+ private int mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
private NotificationRecord mVibrateNotification;
private Vibrator mVibrator = new Vibrator();
@@ -377,6 +378,11 @@
mSettingsObserver.observe();
}
+ void systemReady() {
+ // no beeping until we're basically done booting
+ mSystemReady = true;
+ }
+
// Toasts
// ============================================================================
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
@@ -637,7 +643,7 @@
}
}
- sendAccessibilityEventTypeNotificationChangedDoCheck(notification, pkg);
+ sendAccessibilityEvent(notification, pkg);
} else {
if (old != null && old.statusBarKey != null) {
@@ -654,7 +660,8 @@
// If we're not supposed to beep, vibrate, etc. then don't.
if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
&& (!(old != null
- && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))) {
+ && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
+ && mSystemReady) {
// sound
final boolean useDefaultSound =
(notification.defaults & Notification.DEFAULT_SOUND) != 0;
@@ -721,8 +728,7 @@
idOut[0] = id;
}
- private void sendAccessibilityEventTypeNotificationChangedDoCheck(Notification notification,
- CharSequence packageName) {
+ private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
if (!manager.isEnabled()) {
return;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3e4d5f9..b038a64 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -190,6 +190,7 @@
StatusBarService statusBar = null;
InputMethodManagerService imm = null;
AppWidgetService appWidget = null;
+ NotificationManagerService notification = null;
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
@@ -240,8 +241,8 @@
try {
Log.i(TAG, "Starting Notification Manager.");
- ServiceManager.addService(Context.NOTIFICATION_SERVICE,
- new NotificationManagerService(context, statusBar, hardware));
+ notification = new NotificationManagerService(context, statusBar, hardware);
+ ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);
} catch (Throwable e) {
Log.e(TAG, "Failure starting Notification Manager", e);
}
@@ -348,6 +349,11 @@
// It is now time to start up the app processes...
boolean safeMode = wm.detectSafeMode();
+
+ if (notification != null) {
+ notification.systemReady();
+ }
+
if (statusBar != null) {
statusBar.systemReady();
}
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 2dd70ef..ad882a9 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -53,6 +53,7 @@
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
@@ -417,7 +418,13 @@
final Configuration mTempConfiguration = new Configuration();
int screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
-
+
+ // The frame use to limit the size of the app running in compatibility mode.
+ Rect mCompatibleScreenFrame = new Rect();
+ // The surface used to fill the outer rim of the app running in compatibility mode.
+ Surface mBackgroundFillerSurface = null;
+ boolean mBackgroundFillerShown = false;
+
public static WindowManagerService main(Context context,
PowerManagerService pm, boolean haveInputMethods) {
WMThread thr = new WMThread(context, pm, haveInputMethods);
@@ -3738,12 +3745,14 @@
}
config.orientation = orientation;
+ DisplayMetrics dm = new DisplayMetrics();
+ mDisplay.getMetrics(dm);
+ CompatibilityInfo.updateCompatibleScreenFrame(dm, orientation, mCompatibleScreenFrame);
+
if (screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
// Note we only do this once because at this point we don't
// expect the screen to change in this way at runtime, and want
// to avoid all of this computation for every config change.
- DisplayMetrics dm = new DisplayMetrics();
- mDisplay.getMetrics(dm);
int longSize = dw;
int shortSize = dh;
if (longSize < shortSize) {
@@ -3753,7 +3762,7 @@
}
longSize = (int)(longSize/dm.density);
shortSize = (int)(shortSize/dm.density);
-
+
// These semi-magic numbers define our compatibility modes for
// applications with different screens. Don't change unless you
// make sure to test lots and lots of apps!
@@ -5845,8 +5854,19 @@
public void computeFrameLw(Rect pf, Rect df, Rect cf, Rect vf) {
mHaveFrame = true;
- final int pw = pf.right-pf.left;
- final int ph = pf.bottom-pf.top;
+ final Rect container = mContainingFrame;
+ container.set(pf);
+
+ final Rect display = mDisplayFrame;
+ display.set(df);
+
+ if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW) != 0) {
+ container.intersect(mCompatibleScreenFrame);
+ display.intersect(mCompatibleScreenFrame);
+ }
+
+ final int pw = container.right - container.left;
+ final int ph = container.bottom - container.top;
int w,h;
if ((mAttrs.flags & mAttrs.FLAG_SCALED) != 0) {
@@ -5857,12 +5877,6 @@
h = mAttrs.height== mAttrs.FILL_PARENT ? ph : mRequestedHeight;
}
- final Rect container = mContainingFrame;
- container.set(pf);
-
- final Rect display = mDisplayFrame;
- display.set(df);
-
final Rect content = mContentFrame;
content.set(cf);
@@ -5882,7 +5896,7 @@
// Now make sure the window fits in the overall display.
Gravity.applyDisplay(mAttrs.gravity, df, frame);
-
+
// Make sure the content and visible frames are inside of the
// final window frame.
if (content.left < frame.left) content.left = frame.left;
@@ -6573,16 +6587,29 @@
return false;
}
- boolean isFullscreenOpaque(int screenWidth, int screenHeight) {
- if (mAttrs.format != PixelFormat.OPAQUE || mSurface == null
- || mAnimation != null || mDrawPending || mCommitDrawPending) {
- return false;
- }
- if (mFrame.left <= 0 && mFrame.top <= 0 &&
- mFrame.right >= screenWidth && mFrame.bottom >= screenHeight) {
- return true;
- }
- return false;
+ /**
+ * Return true if the window is opaque and fully drawn.
+ */
+ boolean isOpaqueDrawn() {
+ return mAttrs.format == PixelFormat.OPAQUE && mSurface != null
+ && mAnimation == null && !mDrawPending && !mCommitDrawPending;
+ }
+
+ boolean needsBackgroundFiller(int screenWidth, int screenHeight) {
+ return
+ // only if the application is requesting compatible window
+ (mAttrs.flags & mAttrs.FLAG_COMPATIBLE_WINDOW) != 0 &&
+ // and only if the application wanted to fill the screen
+ mAttrs.width == mAttrs.FILL_PARENT &&
+ mAttrs.height == mAttrs.FILL_PARENT &&
+ // and only if the screen is bigger
+ ((mFrame.right - mFrame.right) < screenWidth ||
+ (mFrame.bottom - mFrame.top) < screenHeight);
+ }
+
+ boolean isFullscreen(int screenWidth, int screenHeight) {
+ return mFrame.left <= 0 && mFrame.top <= 0 &&
+ mFrame.right >= screenWidth && mFrame.bottom >= screenHeight;
}
void removeLocked() {
@@ -8102,6 +8129,7 @@
boolean dimming = false;
boolean covered = false;
boolean syswin = false;
+ boolean backgroundFillerShown = false;
for (i=N-1; i>=0; i--) {
WindowState w = (WindowState)mWindows.get(i);
@@ -8371,11 +8399,39 @@
syswin = true;
}
}
- if (w.isFullscreenOpaque(dw, dh)) {
+
+ boolean opaqueDrawn = w.isOpaqueDrawn();
+ if (opaqueDrawn && w.isFullscreen(dw, dh)) {
// This window completely covers everything behind it,
// so we want to leave all of them as unblurred (for
// performance reasons).
obscured = true;
+ } else if (opaqueDrawn && w.needsBackgroundFiller(dw, dh)) {
+ if (SHOW_TRANSACTIONS) Log.d(TAG, "showing background filler");
+ // This window is in compatibility mode, and needs background filler.
+ obscured = true;
+ if (mBackgroundFillerSurface == null) {
+ try {
+ mBackgroundFillerSurface = new Surface(mFxSession, 0,
+ 0, dw, dh,
+ PixelFormat.OPAQUE,
+ Surface.FX_SURFACE_NORMAL);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating filler surface", e);
+ }
+ }
+ try {
+ mBackgroundFillerSurface.setPosition(0, 0);
+ mBackgroundFillerSurface.setSize(dw, dh);
+ // Using the same layer as Dim because they will never be shown at the
+ // same time.
+ mBackgroundFillerSurface.setLayer(w.mAnimLayer - 1);
+ mBackgroundFillerSurface.show();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Exception showing filler surface");
+ }
+ backgroundFillerShown = true;
+ mBackgroundFillerShown = true;
} else if (canBeSeen && !obscured &&
(attrFlags&FLAG_BLUR_BEHIND|FLAG_DIM_BEHIND) != 0) {
if (localLOGV) Log.v(TAG, "Win " + w
@@ -8472,6 +8528,16 @@
}
}
}
+
+ if (backgroundFillerShown == false && mBackgroundFillerShown) {
+ mBackgroundFillerShown = false;
+ if (SHOW_TRANSACTIONS) Log.d(TAG, "hiding background filler");
+ try {
+ mBackgroundFillerSurface.hide();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Exception hiding filler surface", e);
+ }
+ }
if (!dimming && mDimShown) {
// Time to hide the dim surface... start fading.
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 2fe4dd4..aad542a 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -56,6 +56,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -7072,6 +7073,27 @@
== PackageManager.PERMISSION_GRANTED) {
return null;
}
+
+ PathPermission[] pps = cpi.pathPermissions;
+ if (pps != null) {
+ int i = pps.length;
+ while (i > 0) {
+ i--;
+ PathPermission pp = pps[i];
+ if (checkComponentPermission(pp.getReadPermission(), callingPid, callingUid,
+ cpi.exported ? -1 : cpi.applicationInfo.uid)
+ == PackageManager.PERMISSION_GRANTED
+ && mode == ParcelFileDescriptor.MODE_READ_ONLY || mode == -1) {
+ return null;
+ }
+ if (checkComponentPermission(pp.getWritePermission(), callingPid, callingUid,
+ cpi.exported ? -1 : cpi.applicationInfo.uid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return null;
+ }
+ }
+ }
+
String msg = "Permission Denial: opening provider " + cpi.name
+ " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
+ ", uid=" + callingUid + ") requires "
diff --git a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
index c4f1ab6..4c5fefc 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/SearchManagerTest.java
@@ -107,8 +107,6 @@
}
// Checks that the search UI is not visible.
- // This checks both the SearchManager and the SearchManagerService,
- // since SearchManager keeps a local variable for the visibility.
private void assertSearchNotVisible() {
SearchManager searchManager = (SearchManager)
mContext.getSystemService(Context.SEARCH_SERVICE);
@@ -245,22 +243,4 @@
assertSearchNotVisible();
}
- @MediumTest
- public void testSearchDialogState() throws Exception {
- SearchManager searchManager = (SearchManager)
- mContext.getSystemService(Context.SEARCH_SERVICE);
- assertNotNull(searchManager);
-
- Bundle searchState;
-
- // search dialog not visible, so no state should be stored
- searchState = searchManager.saveSearchDialog();
- assertNull(searchState);
-
- searchManager.startSearch("test search string", true, SEARCHABLE_ACTIVITY, null, false);
- searchState = searchManager.saveSearchDialog();
- assertNotNull(searchState);
- searchManager.stopSearch();
- }
-
}