Merge "Cleanup Bitmap JNI"
diff --git a/Android.mk b/Android.mk
index f3f1acb..e42a2f8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -238,6 +238,8 @@
core/java/android/service/wallpaper/IWallpaperConnection.aidl \
core/java/android/service/wallpaper/IWallpaperEngine.aidl \
core/java/android/service/wallpaper/IWallpaperService.aidl \
+ core/java/android/service/chooser/IChooserTargetService.aidl \
+ core/java/android/service/chooser/IChooserTargetResult.aidl \
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
core/java/android/view/accessibility/IAccessibilityManager.aidl \
@@ -333,8 +335,8 @@
media/java/android/media/IRingtonePlayer.aidl \
media/java/android/media/IVolumeController.aidl \
media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \
+ media/java/android/media/midi/IMidiDeviceListener.aidl \
media/java/android/media/midi/IMidiDeviceServer.aidl \
- media/java/android/media/midi/IMidiListener.aidl \
media/java/android/media/midi/IMidiManager.aidl \
media/java/android/media/projection/IMediaProjection.aidl \
media/java/android/media/projection/IMediaProjectionCallback.aidl \
@@ -545,6 +547,7 @@
frameworks/base/core/java/android/view/textservice/SuggestionsInfo.aidl \
frameworks/base/core/java/android/service/carrier/MessagePdu.aidl \
frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
+ frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
frameworks/base/core/java/android/speech/tts/Voice.aidl \
frameworks/base/core/java/android/app/usage/UsageEvents.aidl \
frameworks/base/core/java/android/app/Notification.aidl \
diff --git a/api/current.txt b/api/current.txt
index 707999b..7a348ea 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21,6 +21,7 @@
field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
field public static final java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
+ field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE";
field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
@@ -3544,6 +3545,7 @@
method public int getLargeMemoryClass();
method public int getLauncherLargeIconDensity();
method public int getLauncherLargeIconSize();
+ method public int getLockTaskModeState();
method public int getMemoryClass();
method public void getMemoryInfo(android.app.ActivityManager.MemoryInfo);
method public static void getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo);
@@ -3554,7 +3556,7 @@
method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException;
method public java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
method public deprecated java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException;
- method public boolean isInLockTaskMode();
+ method public deprecated boolean isInLockTaskMode();
method public boolean isLowRamDevice();
method public static boolean isRunningInTestHarness();
method public static boolean isUserAMonkey();
@@ -3562,6 +3564,9 @@
method public void moveTaskToFront(int, int);
method public void moveTaskToFront(int, int, android.os.Bundle);
method public deprecated void restartPackage(java.lang.String);
+ field public static final int LOCK_TASK_MODE_LOCKED = 1; // 0x1
+ field public static final int LOCK_TASK_MODE_NONE = 0; // 0x0
+ field public static final int LOCK_TASK_MODE_PINNED = 2; // 0x2
field public static final java.lang.String META_HOME_ALTERNATE = "android.app.home.alternate";
field public static final int MOVE_TASK_NO_USER_ACTION = 2; // 0x2
field public static final int MOVE_TASK_WITH_HOME = 1; // 0x1
@@ -5429,6 +5434,7 @@
method public void onPasswordFailed(android.content.Context, android.content.Intent);
method public void onPasswordSucceeded(android.content.Context, android.content.Intent);
method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
+ method public void onReadyForUserInitialization(android.content.Context, android.content.Intent);
method public void onReceive(android.content.Context, android.content.Intent);
field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
@@ -5440,6 +5446,7 @@
field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED";
field public static final java.lang.String ACTION_PASSWORD_SUCCEEDED = "android.app.action.ACTION_PASSWORD_SUCCEEDED";
field public static final java.lang.String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.PROFILE_PROVISIONING_COMPLETE";
+ field public static final java.lang.String ACTION_READY_FOR_USER_INITIALIZATION = "android.app.action.READY_FOR_USER_INITIALIZATION";
field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
@@ -5451,6 +5458,7 @@
method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
method public void addUserRestriction(android.content.ComponentName, java.lang.String);
method public void clearCrossProfileIntentFilters(android.content.ComponentName);
+ method public void clearDeviceInitializerApp(android.content.ComponentName);
method public void clearDeviceOwnerApp(java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
@@ -5494,6 +5502,7 @@
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
+ method public boolean isDeviceInitializerApp(java.lang.String);
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isLockTaskPermitted(java.lang.String);
method public boolean isMasterVolumeMuted(android.content.ComponentName);
@@ -5510,6 +5519,7 @@
method public void setAutoTimeRequired(android.content.ComponentName, boolean);
method public void setCameraDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+ method public boolean setDeviceInitializer(android.content.ComponentName, android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
@@ -5536,6 +5546,7 @@
method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public int setStorageEncryption(android.content.ComponentName, boolean);
method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean);
+ method public boolean setUserEnabled(android.content.ComponentName);
method public boolean switchUser(android.content.ComponentName, android.os.UserHandle);
method public void uninstallAllUserCaCerts(android.content.ComponentName);
method public void uninstallCaCert(android.content.ComponentName, byte[]);
@@ -5557,6 +5568,10 @@
field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER";
field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION";
field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION";
field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
@@ -27449,6 +27464,30 @@
}
+package android.service.chooser {
+
+ public final class ChooserTarget implements android.os.Parcelable {
+ ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.app.PendingIntent);
+ ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.content.IntentSender);
+ method public int describeContents();
+ method public android.graphics.Bitmap getIcon();
+ method public android.content.IntentSender getIntentSender();
+ method public float getScore();
+ method public java.lang.CharSequence getTitle();
+ method public boolean sendIntent(android.content.Context, android.content.Intent);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.chooser.ChooserTarget> CREATOR;
+ }
+
+ public abstract class ChooserTargetService extends android.app.Service {
+ ctor public ChooserTargetService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract java.util.List<android.service.chooser.ChooserTarget> onGetChooserTargets(android.content.ComponentName, android.content.IntentFilter);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.chooser.ChooserTargetService";
+ }
+
+}
+
package android.service.dreams {
public class DreamService extends android.app.Service implements android.view.Window.Callback {
diff --git a/api/system-current.txt b/api/system-current.txt
index 2ffc4e18..4755fe7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -29,6 +29,7 @@
field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
field public static final java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
+ field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE";
field public static final java.lang.String BIND_CONDITION_PROVIDER_SERVICE = "android.permission.BIND_CONDITION_PROVIDER_SERVICE";
field public static final java.lang.String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
@@ -3632,6 +3633,7 @@
method public int getLargeMemoryClass();
method public int getLauncherLargeIconDensity();
method public int getLauncherLargeIconSize();
+ method public int getLockTaskModeState();
method public int getMemoryClass();
method public void getMemoryInfo(android.app.ActivityManager.MemoryInfo);
method public static void getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo);
@@ -3642,7 +3644,7 @@
method public android.app.PendingIntent getRunningServiceControlPanel(android.content.ComponentName) throws java.lang.SecurityException;
method public java.util.List<android.app.ActivityManager.RunningServiceInfo> getRunningServices(int) throws java.lang.SecurityException;
method public deprecated java.util.List<android.app.ActivityManager.RunningTaskInfo> getRunningTasks(int) throws java.lang.SecurityException;
- method public boolean isInLockTaskMode();
+ method public deprecated boolean isInLockTaskMode();
method public boolean isLowRamDevice();
method public static boolean isRunningInTestHarness();
method public static boolean isUserAMonkey();
@@ -3650,6 +3652,9 @@
method public void moveTaskToFront(int, int);
method public void moveTaskToFront(int, int, android.os.Bundle);
method public deprecated void restartPackage(java.lang.String);
+ field public static final int LOCK_TASK_MODE_LOCKED = 1; // 0x1
+ field public static final int LOCK_TASK_MODE_NONE = 0; // 0x0
+ field public static final int LOCK_TASK_MODE_PINNED = 2; // 0x2
field public static final java.lang.String META_HOME_ALTERNATE = "android.app.home.alternate";
field public static final int MOVE_TASK_NO_USER_ACTION = 2; // 0x2
field public static final int MOVE_TASK_WITH_HOME = 1; // 0x1
@@ -5523,6 +5528,7 @@
method public void onPasswordFailed(android.content.Context, android.content.Intent);
method public void onPasswordSucceeded(android.content.Context, android.content.Intent);
method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent);
+ method public void onReadyForUserInitialization(android.content.Context, android.content.Intent);
method public void onReceive(android.content.Context, android.content.Intent);
field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
@@ -5534,6 +5540,7 @@
field public static final java.lang.String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED";
field public static final java.lang.String ACTION_PASSWORD_SUCCEEDED = "android.app.action.ACTION_PASSWORD_SUCCEEDED";
field public static final java.lang.String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.PROFILE_PROVISIONING_COMPLETE";
+ field public static final java.lang.String ACTION_READY_FOR_USER_INITIALIZATION = "android.app.action.READY_FOR_USER_INITIALIZATION";
field public static final java.lang.String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
field public static final java.lang.String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
@@ -5545,6 +5552,7 @@
method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName);
method public void addUserRestriction(android.content.ComponentName, java.lang.String);
method public void clearCrossProfileIntentFilters(android.content.ComponentName);
+ method public void clearDeviceInitializerApp(android.content.ComponentName);
method public void clearDeviceOwnerApp(java.lang.String);
method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
method public void clearProfileOwner(android.content.ComponentName);
@@ -5561,6 +5569,7 @@
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
+ method public java.lang.String getDeviceInitializerApp();
method public java.lang.String getDeviceOwner();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -5594,6 +5603,7 @@
method public boolean isActivePasswordSufficient();
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
+ method public boolean isDeviceInitializerApp(java.lang.String);
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isLockTaskPermitted(java.lang.String);
method public boolean isMasterVolumeMuted(android.content.ComponentName);
@@ -5611,6 +5621,7 @@
method public void setAutoTimeRequired(android.content.ComponentName, boolean);
method public void setCameraDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+ method public boolean setDeviceInitializer(android.content.ComponentName, android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
@@ -5637,6 +5648,7 @@
method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public int setStorageEncryption(android.content.ComponentName, boolean);
method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean);
+ method public boolean setUserEnabled(android.content.ComponentName);
method public boolean switchUser(android.content.ComponentName, android.os.UserHandle);
method public void uninstallAllUserCaCerts(android.content.ComponentName);
method public void uninstallCaCert(android.content.ComponentName, byte[]);
@@ -5660,6 +5672,10 @@
field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER";
field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION";
field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER";
+ field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION";
field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
@@ -29049,6 +29065,30 @@
}
+package android.service.chooser {
+
+ public final class ChooserTarget implements android.os.Parcelable {
+ ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.app.PendingIntent);
+ ctor public ChooserTarget(java.lang.CharSequence, android.graphics.Bitmap, float, android.content.IntentSender);
+ method public int describeContents();
+ method public android.graphics.Bitmap getIcon();
+ method public android.content.IntentSender getIntentSender();
+ method public float getScore();
+ method public java.lang.CharSequence getTitle();
+ method public boolean sendIntent(android.content.Context, android.content.Intent);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.chooser.ChooserTarget> CREATOR;
+ }
+
+ public abstract class ChooserTargetService extends android.app.Service {
+ ctor public ChooserTargetService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract java.util.List<android.service.chooser.ChooserTarget> onGetChooserTargets(android.content.ComponentName, android.content.IntentFilter);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.chooser.ChooserTargetService";
+ }
+
+}
+
package android.service.dreams {
public class DreamService extends android.app.Service implements android.view.Window.Callback {
@@ -30894,8 +30934,8 @@
method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
field public static final java.lang.String ACTION_CONNECTION_SERVICE_CONFIGURE = "android.telecom.action.CONNECTION_SERVICE_CONFIGURE";
- field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final java.lang.String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED";
+ field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
field public static final java.lang.String ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS = "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS";
field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
diff --git a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
index a31b150..c27d0c0 100644
--- a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
+++ b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
@@ -20,6 +20,7 @@
import android.app.IActivityManager;
import android.app.IActivityManager.ContentProviderHolder;
import android.content.IContentProvider;
+import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -29,13 +30,18 @@
import android.os.UserHandle;
import android.provider.Settings;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
public final class SettingsCmd {
enum CommandVerb {
UNSPECIFIED,
GET,
PUT,
- DELETE
+ DELETE,
+ LIST,
}
static String[] mArgs;
@@ -47,7 +53,7 @@
String mValue = null;
public static void main(String[] args) {
- if (args == null || args.length < 3) {
+ if (args == null || args.length < 2) {
printUsage();
return;
}
@@ -78,6 +84,8 @@
mVerb = CommandVerb.PUT;
} else if ("delete".equalsIgnoreCase(arg)) {
mVerb = CommandVerb.DELETE;
+ } else if ("list".equalsIgnoreCase(arg)) {
+ mVerb = CommandVerb.LIST;
} else {
// invalid
System.err.println("Invalid command: " + arg);
@@ -91,6 +99,10 @@
break; // invalid
}
mTable = arg.toLowerCase();
+ if (mVerb == CommandVerb.LIST) {
+ valid = true;
+ break;
+ }
} else if (mVerb == CommandVerb.GET || mVerb == CommandVerb.DELETE) {
mKey = arg;
if (mNextArg >= mArgs.length) {
@@ -144,6 +156,11 @@
System.out.println("Deleted "
+ deleteForUser(provider, mUser, mTable, mKey) + " rows");
break;
+ case LIST:
+ for (String line : listForUser(provider, mUser, mTable)) {
+ System.out.println(line);
+ }
+ break;
default:
System.err.println("Unspecified command");
break;
@@ -164,6 +181,34 @@
}
}
+ private List<String> listForUser(IContentProvider provider, int userHandle, String table) {
+ final Uri uri = "system".equals(table) ? Settings.System.CONTENT_URI
+ : "secure".equals(table) ? Settings.Secure.CONTENT_URI
+ : "global".equals(table) ? Settings.Global.CONTENT_URI
+ : null;
+ final ArrayList<String> lines = new ArrayList<String>();
+ if (uri == null) {
+ return lines;
+ }
+ try {
+ final Cursor cursor = provider.query(resolveCallingPackage(), uri, null, null, null,
+ null, null);
+ try {
+ while (cursor != null && cursor.moveToNext()) {
+ lines.add(cursor.getString(1) + "=" + cursor.getString(2));
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ Collections.sort(lines);
+ } catch (RemoteException e) {
+ System.err.println("List failed in " + table + " for user " + userHandle);
+ }
+ return lines;
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -244,6 +289,7 @@
System.err.println("usage: settings [--user NUM] get namespace key");
System.err.println(" settings [--user NUM] put namespace key value");
System.err.println(" settings [--user NUM] delete namespace key");
+ System.err.println(" settings [--user NUM] list namespace");
System.err.println("\n'namespace' is one of {system, secure, global}, case-insensitive");
System.err.println("If '--user NUM' is not given, the operations are performed on the owner user.");
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c6ffef6..c525ef2 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -309,6 +309,21 @@
/** @hide Process is being cached for later use and is empty. */
public static final int PROCESS_STATE_CACHED_EMPTY = 13;
+ /**
+ * Lock task mode is not active.
+ */
+ public static final int LOCK_TASK_MODE_NONE = 0;
+
+ /**
+ * Full lock task mode is active.
+ */
+ public static final int LOCK_TASK_MODE_LOCKED = 1;
+
+ /**
+ * App pinning mode is active.
+ */
+ public static final int LOCK_TASK_MODE_PINNED = 2;
+
Point mAppTaskThumbnailSize;
/*package*/ ActivityManager(Context context, Handler handler) {
@@ -2684,12 +2699,25 @@
* no new tasks can be created or switched to.
*
* @see Activity#startLockTask()
+ *
+ * @deprecated Use {@link #getLockTaskModeState} instead.
*/
public boolean isInLockTaskMode() {
+ return getLockTaskModeState() != LOCK_TASK_MODE_NONE;
+ }
+
+ /**
+ * Return the current state of task locking. The three possible outcomes
+ * are {@link #LOCK_TASK_MODE_NONE}, {@link #LOCK_TASK_MODE_LOCKED}
+ * and {@link #LOCK_TASK_MODE_PINNED}.
+ *
+ * @see Activity#startLockTask()
+ */
+ public int getLockTaskModeState() {
try {
- return ActivityManagerNative.getDefault().isInLockTaskMode();
+ return ActivityManagerNative.getDefault().getLockTaskModeState();
} catch (RemoteException e) {
- return false;
+ return ActivityManager.LOCK_TASK_MODE_NONE;
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index bb307bb..3197461 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2294,6 +2294,14 @@
return true;
}
+ case GET_LOCK_TASK_MODE_STATE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final int lockTaskModeState = getLockTaskModeState();
+ reply.writeNoException();
+ reply.writeInt(lockTaskModeState);
+ return true;
+ }
+
case SET_TASK_DESCRIPTION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -5420,6 +5428,19 @@
}
@Override
+ public int getLockTaskModeState() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_LOCK_TASK_MODE_STATE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int lockTaskModeState = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return lockTaskModeState;
+ }
+
+ @Override
public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index a7e9413..1277cfa 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -461,6 +461,8 @@
public boolean isInLockTaskMode() throws RemoteException;
+ public int getLockTaskModeState() throws RemoteException;
+
public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
throws RemoteException;
public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException;
@@ -810,4 +812,5 @@
int SET_TASK_RESIZEABLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+283;
int REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+284;
int RESIZE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+285;
+ int GET_LOCK_TASK_MODE_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+286;
}
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 4fc990e..fe284ce 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -168,8 +168,8 @@
/**
* Action sent to a device administrator to notify that the device is entering
- * lock task mode from an authorized package. The extra {@link #EXTRA_LOCK_TASK_PACKAGE}
- * will describe the authorized package using lock task mode.
+ * lock task mode. The extra {@link #EXTRA_LOCK_TASK_PACKAGE}
+ * will describe the package using lock task mode.
*
* <p>The calling device admin must be the device owner or profile
* owner to receive this broadcast.
@@ -182,7 +182,7 @@
/**
* Action sent to a device administrator to notify that the device is exiting
- * lock task mode from an authorized package.
+ * lock task mode.
*
* <p>The calling device admin must be the device owner or profile
* owner to receive this broadcast.
@@ -215,7 +215,8 @@
* <p>A device admin application which listens to this intent can find out if the device was
* provisioned for the device owner or profile owner case by calling respectively
* {@link android.app.admin.DevicePolicyManager#isDeviceOwnerApp} and
- * {@link android.app.admin.DevicePolicyManager#isProfileOwnerApp}.
+ * {@link android.app.admin.DevicePolicyManager#isProfileOwnerApp}. You will generally handle
+ * this in {@link DeviceAdminReceiver#onProfileProvisioningComplete}.
*
* <p>Input: Nothing.</p>
* <p>Output: Nothing</p>
@@ -224,6 +225,23 @@
public static final String ACTION_PROFILE_PROVISIONING_COMPLETE =
"android.app.action.PROFILE_PROVISIONING_COMPLETE";
+ /**
+ * Broadcast Action: This broadcast is sent to indicate that the system is ready for the device
+ * initializer to perform user setup tasks. This is only applicable to devices managed by a
+ * device owner app.
+ *
+ * <p>The broadcast will be limited to the {@link DeviceAdminReceiver} component specified in
+ * the (@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME) field
+ * of the original intent or NFC bump that started the provisioning process. You will generally
+ * handle this in {@link DeviceAdminReceiver#onReadyForUserInitialization}.
+ *
+ * <p>Input: Nothing.</p>
+ * <p>Output: Nothing</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_READY_FOR_USER_INITIALIZATION =
+ "android.app.action.READY_FOR_USER_INITIALIZATION";
+
/** @hide */
public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS";
@@ -245,7 +263,7 @@
/** @hide */
public static final String EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE = "android.app.extra.CHOOSE_PRIVATE_KEY_RESPONSE";
- /**
+ /**
* Name under which a DevicePolicy component publishes information
* about itself. This meta-data must reference an XML resource containing
* a device-admin tag.
@@ -382,20 +400,20 @@
/**
* Called when provisioning of a managed profile or managed device has completed successfully.
*
- * <p> As a prerequisit for the execution of this callback the (@link DeviceAdminReceiver} has
+ * <p> As a prerequisite for the execution of this callback the {@link DeviceAdminReceiver} has
* to declare an intent filter for {@link #ACTION_PROFILE_PROVISIONING_COMPLETE}.
* Its component must also be specified in the {@link DevicePolicyManager#EXTRA_DEVICE_ADMIN}
* of the {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} intent that started the
* managed provisioning.
*
- * <p>When provisioning is complete, the managed profile is hidden until the profile owner
- * calls {DevicePolicyManager#setProfileEnabled(ComponentName admin)}. Typically a profile
- * owner will enable the profile when it has finished any additional setup such as adding an
- * account by using the {@link AccountManager} and calling apis to bring the profile into the
- * desired state.
+ * <p>When provisioning of a managed profile is complete, the managed profile is hidden until
+ * the profile owner calls {DevicePolicyManager#setProfileEnabled(ComponentName admin)}.
+ * Typically a profile owner will enable the profile when it has finished any additional setup
+ * such as adding an account by using the {@link AccountManager} and calling apis to bring the
+ * profile into the desired state.
*
* <p> Note that provisioning completes without waiting for any server interactions, so the
- * profile owner needs to wait for data to be available if required (e.g android device ids or
+ * profile owner needs to wait for data to be available if required (e.g. android device ids or
* other data that is set as a result of server interactions).
*
* @param context The running context as per {@link #onReceive}.
@@ -405,8 +423,31 @@
}
/**
- * Called when a device is entering lock task mode by a package authorized
- * by {@link DevicePolicyManager#isLockTaskPermitted(String)}
+ * Called during provisioning of a managed device to allow the device initializer to perform
+ * user setup steps. Only device initializers should override this method.
+ *
+ * <p> Called when the DeviceAdminReceiver receives a
+ * {@link #ACTION_READY_FOR_USER_INITIALIZATION} broadcast. As a prerequisite for the execution
+ * of this callback the {@link DeviceAdminReceiver} has
+ * to declare an intent filter for {@link #ACTION_READY_FOR_USER_INITIALIZATION}. Only the
+ * component specified in the
+ * {@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME} field of the
+ * original intent or NFC bump that started the provisioning process will receive this callback.
+ *
+ * <p>It is not assumed that the device initializer is finished when it returns from
+ * this call, as it may do additional setup asynchronously. The device initializer must call
+ * {DevicePolicyManager#setUserEnabled(ComponentName admin)} when it has finished any additional
+ * setup (such as adding an account by using the {@link AccountManager}) in order for the user
+ * to be functional.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onReadyForUserInitialization(Context context, Intent intent) {
+ }
+
+ /**
+ * Called when a device is entering lock task mode.
*
* @param context The running context as per {@link #onReceive}.
* @param intent The received intent as per {@link #onReceive}.
@@ -416,8 +457,7 @@
}
/**
- * Called when a device is exiting lock task mode by a package authorized
- * by {@link DevicePolicyManager#isLockTaskPermitted(String)}
+ * Called when a device is exiting lock task mode.
*
* @param context The running context as per {@link #onReceive}.
* @param intent The received intent as per {@link #onReceive}.
@@ -488,6 +528,8 @@
onLockTaskModeEntering(context, intent, pkg);
} else if (ACTION_LOCK_TASK_EXITING.equals(action)) {
onLockTaskModeExiting(context, intent);
+ } else if (ACTION_READY_FOR_USER_INITIALIZATION.equals(action)) {
+ onReadyForUserInitialization(context, intent);
}
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 170224d..3d587b6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -357,6 +357,52 @@
"android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
/**
+ * On devices managed by a device owner app, a String representation of a Component name extra
+ * indicating the component of the application that is temporarily granted device owner
+ * privileges during device initialization and profile owner privileges during secondary user
+ * initialization.
+ *
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
+ * @see ComponentName#unflattenFromString()
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME
+ = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME";
+
+ /**
+ * A String extra holding an http url that specifies the download location of the device
+ * initializer package. When not provided it is assumed that the device initializer package is
+ * already installed.
+ *
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION
+ = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION";
+
+ /**
+ * A String extra holding a http cookie header which should be used in the http request to the
+ * url specified in {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}.
+ *
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER
+ = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER";
+
+ /**
+ * A String extra holding the SHA-1 checksum of the file at download location specified in
+ * {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}. If this doesn't
+ * match the file at the download location an error will be shown to the user and the user will
+ * be asked to factory reset the device.
+ *
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM
+ = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM";
+
+ /**
* This MIME type is used for starting the Device Owner provisioning.
*
* <p>During device owner provisioning a device admin app is set as the owner of the device.
@@ -2382,6 +2428,113 @@
}
/**
+ * Sets the given component as the device initializer. The package must already be installed and
+ * set as an active device administrator, and there must not be an existing device initializer,
+ * for this call to succeed. This method can only be called by an app holding the
+ * MANAGE_DEVICE_ADMINS permission before the device is provisioned or by a device owner app. A
+ * device initializer app is granted device owner privileges during device initialization and
+ * profile owner privileges during secondary user initialization.
+ * @param who Which {@link DeviceAdminReceiver} this request is associated with, or null if not
+ * called by the device owner.
+ * @param initializer Which {@link DeviceAdminReceiver} to make device initializer.
+ * @param initializerName The user-visible name of the device initializer.
+ * @return whether the package was successfully registered as the device initializer.
+ * @throws IllegalArgumentException if the package name is null or invalid
+ * @throws IllegalStateException if the caller is not device owner or the device has
+ * already been provisioned or a device initializer already exists.
+ */
+ public boolean setDeviceInitializer(ComponentName who, ComponentName initializer,
+ String initializerName) throws IllegalArgumentException, IllegalStateException {
+ if (mService != null) {
+ try {
+ return mService.setDeviceInitializer(who, initializer, initializerName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to set device initializer");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Used to determine if a particular package has been registered as the device initializer.
+ *
+ * @param packageName the package name of the app, to compare with the registered device
+ * initializer app, if any.
+ * @return whether or not the caller is registered as the device initializer app.
+ */
+ public boolean isDeviceInitializerApp(String packageName) {
+ if (mService != null) {
+ try {
+ return mService.isDeviceInitializer(packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to check device initializer");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes the device initializer, so that it will not be invoked on user initialization for any
+ * subsequently created users. This method can be called by either the device owner or device
+ * initializer itself. The caller must be an active administrator.
+ *
+ * @param who Which {@link DeviceAdminReceiver} this request is associated with.
+ */
+ public void clearDeviceInitializerApp(ComponentName who) {
+ if (mService != null) {
+ try {
+ mService.clearDeviceInitializer(who);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to clear device initializer");
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Gets the device initializer of the system.
+ *
+ * @return the package name of the device initializer.
+ */
+ @SystemApi
+ public String getDeviceInitializerApp() {
+ if (mService != null) {
+ try {
+ return mService.getDeviceInitializer();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to get device initializer");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the enabled state of the user. A user should be enabled only once it is ready to
+ * be used.
+ *
+ * <p>Device initializer must call this method to mark the user as functional.
+ * Only the device initializer agent can call this.
+ *
+ * <p>When the user is enabled, if the device initializer is not also the device owner, the
+ * device initializer will no longer have elevated permissions to call methods in this class.
+ * Additionally, it will be removed as an active administrator and its
+ * {@link DeviceAdminReceiver} will be disabled.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return whether the user is now enabled.
+ */
+ public boolean setUserEnabled(ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.setUserEnabled(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
* @hide
* @deprecated Use #ACTION_SET_PROFILE_OWNER
* Sets the given component as an active admin and registers the package as the profile
@@ -3119,8 +3272,7 @@
}
/**
- * Called by a profile or device owner to set a user restriction specified
- * by the key.
+ * Called by a profile or device owner to set a user restriction specified by the key.
* <p>
* The calling device admin must be a profile or device owner; if it is not,
* a security exception will be thrown.
@@ -3141,8 +3293,7 @@
}
/**
- * Called by a profile or device owner to clear a user restriction specified
- * by the key.
+ * Called by a profile or device owner to clear a user restriction specified by the key.
* <p>
* The calling device admin must be a profile or device owner; if it is not,
* a security exception will be thrown.
@@ -3163,7 +3314,7 @@
}
/**
- * Called by device or profile owner to hide or unhide packages. When a package is hidden it
+ * Called by profile or device owners to hide or unhide packages. When a package is hidden it
* is unavailable for use, but the data and actual package file remain.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -3185,7 +3336,7 @@
}
/**
- * Called by device or profile owner to determine if a package is hidden.
+ * Called by profile or device owners to determine if a package is hidden.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param packageName The name of the package to retrieve the hidden status of.
@@ -3203,7 +3354,7 @@
}
/**
- * Called by profile or device owner to re-enable a system app that was disabled by default
+ * Called by profile or device owners to re-enable a system app that was disabled by default
* when the user was initialized.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -3220,7 +3371,7 @@
}
/**
- * Called by profile or device owner to re-enable system apps by intent that were disabled
+ * Called by profile or device owners to re-enable system apps by intent that were disabled
* by default when the user was initialized.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 714e740..5cbab4b 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -199,4 +199,10 @@
boolean getAutoTimeRequired();
boolean isRemovingAdmin(in ComponentName adminReceiver, int userHandle);
+
+ boolean setUserEnabled(in ComponentName who);
+ boolean isDeviceInitializer(String packageName);
+ void clearDeviceInitializer(in ComponentName who);
+ boolean setDeviceInitializer(in ComponentName who, in ComponentName initializer, String initializerName);
+ String getDeviceInitializer();
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 43d3a71..0fee4b3 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -635,24 +635,34 @@
while (i < N && (c=value.charAt(i)) != '-') {
i++;
switch (c) {
- case 'f': out |= ((Display.STATE_OFF-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT); break;
- case 'o': out |= ((Display.STATE_ON-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT); break;
- case 'd': out |= ((Display.STATE_DOZE-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT); break;
- case 'z': out |= ((Display.STATE_DOZE_SUSPEND-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
+ case 'f': out |= (((long)Display.STATE_OFF-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
break;
- case 'p': out |= (STEP_LEVEL_MODE_POWER_SAVE<<STEP_LEVEL_INITIAL_MODE_SHIFT);
+ case 'o': out |= (((long)Display.STATE_ON-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
break;
- case 'F': out |= ((Display.STATE_OFF-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT); break;
- case 'O': out |= ((Display.STATE_ON-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT); break;
- case 'D': out |= ((Display.STATE_DOZE-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT); break;
- case 'Z': out |= ((Display.STATE_DOZE_SUSPEND-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ case 'd': out |= (((long)Display.STATE_DOZE-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
break;
- case 'P': out |= (STEP_LEVEL_MODE_POWER_SAVE<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ case 'z': out |= (((long)Display.STATE_DOZE_SUSPEND-1)
+ << STEP_LEVEL_INITIAL_MODE_SHIFT);
+ break;
+ case 'p': out |= (((long)STEP_LEVEL_MODE_POWER_SAVE)
+ << STEP_LEVEL_INITIAL_MODE_SHIFT);
+ break;
+ case 'F': out |= (((long)Display.STATE_OFF-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ break;
+ case 'O': out |= (((long)Display.STATE_ON-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ break;
+ case 'D': out |= (((long)Display.STATE_DOZE-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ break;
+ case 'Z': out |= (((long)Display.STATE_DOZE_SUSPEND-1)
+ << STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ break;
+ case 'P': out |= (((long)STEP_LEVEL_MODE_POWER_SAVE)
+ << STEP_LEVEL_MODIFIED_MODE_SHIFT);
break;
}
}
i++;
- int level = 0;
+ long level = 0;
while (i < N && (c=value.charAt(i)) != '-') {
i++;
level <<= 4;
@@ -664,6 +674,7 @@
level += c - 'A' + 10;
}
}
+ i++;
out |= (level << STEP_LEVEL_LEVEL_SHIFT) & STEP_LEVEL_LEVEL_MASK;
long duration = 0;
while (i < N && (c=value.charAt(i)) != '-') {
diff --git a/media/java/android/media/midi/IMidiListener.aidl b/core/java/android/service/chooser/ChooserTarget.aidl
similarity index 66%
copy from media/java/android/media/midi/IMidiListener.aidl
copy to core/java/android/service/chooser/ChooserTarget.aidl
index a4129e9..ca91bc8 100644
--- a/media/java/android/media/midi/IMidiListener.aidl
+++ b/core/java/android/service/chooser/ChooserTarget.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -14,13 +14,6 @@
* limitations under the License.
*/
-package android.media.midi;
+package android.service.chooser;
-import android.media.midi.MidiDeviceInfo;
-
-/** @hide */
-oneway interface IMidiListener
-{
- void onDeviceAdded(in MidiDeviceInfo device);
- void onDeviceRemoved(in MidiDeviceInfo device);
-}
+parcelable ChooserTarget;
diff --git a/core/java/android/service/chooser/ChooserTarget.java b/core/java/android/service/chooser/ChooserTarget.java
new file mode 100644
index 0000000..7fd1d10
--- /dev/null
+++ b/core/java/android/service/chooser/ChooserTarget.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2015 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.service.chooser;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A ChooserTarget represents a deep-link into an application as returned by a
+ * {@link android.service.chooser.ChooserTargetService}.
+ */
+public final class ChooserTarget implements Parcelable {
+ private static final String TAG = "ChooserTarget";
+
+ /**
+ * The title of this target that will be shown to the user. The title may be truncated
+ * if it is too long to display in the space provided.
+ */
+ private CharSequence mTitle;
+
+ /**
+ * The icon that will be shown to the user to represent this target.
+ * The system may resize this icon as appropriate.
+ */
+ private Bitmap mIcon;
+
+ /**
+ * The IntentSender that will be used to deliver the intent to the target.
+ * It will be {@link android.content.Intent#fillIn(android.content.Intent, int)} filled in}
+ * by the real intent sent by the application.
+ */
+ private IntentSender mIntentSender;
+
+ /**
+ * The score given to this item. It can be normalized.
+ */
+ private float mScore;
+
+ /**
+ * Construct a deep link target for presentation by a chooser UI.
+ *
+ * <p>A target is composed of a title and an icon for presentation to the user.
+ * The UI presenting this target may truncate the title if it is too long to be presented
+ * in the available space, as well as crop, resize or overlay the supplied icon.</p>
+ *
+ * <p>The creator of a target may supply a ranking score. This score is assumed to be relative
+ * to the other targets supplied by the same
+ * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}.
+ * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match).</p>
+ *
+ * <p>Before being sent, the PendingIntent supplied will be
+ * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied
+ * to the chooser. When constructing a PendingIntent for use in a ChooserTarget, make sure
+ * that you permit the relevant fields to be filled in using the appropriate flags such as
+ * {@link Intent#FILL_IN_ACTION}, {@link Intent#FILL_IN_CATEGORIES},
+ * {@link Intent#FILL_IN_DATA} and {@link Intent#FILL_IN_CLIP_DATA}. Note that
+ * {@link Intent#FILL_IN_CLIP_DATA} is required to appropriately receive URI permission grants
+ * for {@link Intent#ACTION_SEND} intents.</p>
+ *
+ * <p>Take care not to place custom {@link android.os.Parcelable} types into
+ * the PendingIntent as extras, as the system will not be able to unparcel it to merge
+ * additional extras.</p>
+ *
+ * @param title title of this target that will be shown to a user
+ * @param icon icon to represent this target
+ * @param score ranking score for this target between 0.0f and 1.0f, inclusive
+ * @param pendingIntent PendingIntent to fill in and send if the user chooses this target
+ */
+ public ChooserTarget(CharSequence title, Bitmap icon, float score,
+ PendingIntent pendingIntent) {
+ this(title, icon, score, pendingIntent.getIntentSender());
+ }
+
+ /**
+ * Construct a deep link target for presentation by a chooser UI.
+ *
+ * <p>A target is composed of a title and an icon for presentation to the user.
+ * The UI presenting this target may truncate the title if it is too long to be presented
+ * in the available space, as well as crop, resize or overlay the supplied icon.</p>
+ *
+ * <p>The creator of a target may supply a ranking score. This score is assumed to be relative
+ * to the other targets supplied by the same
+ * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}.
+ * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match).</p>
+ *
+ * <p>Before being sent, the IntentSender supplied will be
+ * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied
+ * to the chooser. When constructing an IntentSender for use in a ChooserTarget, make sure
+ * that you permit the relevant fields to be filled in using the appropriate flags such as
+ * {@link Intent#FILL_IN_ACTION}, {@link Intent#FILL_IN_CATEGORIES},
+ * {@link Intent#FILL_IN_DATA} and {@link Intent#FILL_IN_CLIP_DATA}. Note that
+ * {@link Intent#FILL_IN_CLIP_DATA} is required to appropriately receive URI permission grants
+ * for {@link Intent#ACTION_SEND} intents.</p>
+ *
+ * <p>Take care not to place custom {@link android.os.Parcelable} types into
+ * the IntentSender as extras, as the system will not be able to unparcel it to merge
+ * additional extras.</p>
+ *
+ * @param title title of this target that will be shown to a user
+ * @param icon icon to represent this target
+ * @param score ranking score for this target between 0.0f and 1.0f, inclusive
+ * @param intentSender IntentSender to fill in and send if the user chooses this target
+ */
+ public ChooserTarget(CharSequence title, Bitmap icon, float score, IntentSender intentSender) {
+ mTitle = title;
+ mIcon = icon;
+ if (score > 1.f || score < 0.f) {
+ throw new IllegalArgumentException("Score " + score + " out of range; "
+ + "must be between 0.0f and 1.0f");
+ }
+ mScore = score;
+ mIntentSender = intentSender;
+ }
+
+ ChooserTarget(Parcel in) {
+ mTitle = in.readCharSequence();
+ if (in.readInt() != 0) {
+ mIcon = Bitmap.CREATOR.createFromParcel(in);
+ } else {
+ mIcon = null;
+ }
+ mScore = in.readFloat();
+ mIntentSender = IntentSender.readIntentSenderOrNullFromParcel(in);
+ }
+
+ /**
+ * Returns the title of this target for display to a user. The UI displaying the title
+ * may truncate this title if it is too long to be displayed in full.
+ *
+ * @return the title of this target, intended to be shown to a user
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the icon representing this target for display to a user. The UI displaying the icon
+ * may crop, resize or overlay this icon.
+ *
+ * @return the icon representing this target, intended to be shown to a user
+ */
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns the ranking score supplied by the creator of this ChooserTarget.
+ * Values are between 0.0f and 1.0f. The UI displaying the target may
+ * take this score into account when sorting and merging targets from multiple sources.
+ *
+ * @return the ranking score for this target between 0.0f and 1.0f, inclusive
+ */
+ public float getScore() {
+ return mScore;
+ }
+
+ /**
+ * Returns the raw IntentSender supplied by the ChooserTarget's creator.
+ *
+ * <p>To fill in and send the intent, see {@link #sendIntent(Context, Intent)}.</p>
+ *
+ * @return the IntentSender supplied by the ChooserTarget's creator
+ */
+ public IntentSender getIntentSender() {
+ return mIntentSender;
+ }
+
+ /**
+ * Fill in the IntentSender supplied by the ChooserTarget's creator and send it.
+ *
+ * @param context the sending Context; generally the Activity presenting the chooser UI
+ * @param fillInIntent the Intent provided to the Chooser to be sent to a selected target
+ * @return true if sending the Intent was successful
+ */
+ public boolean sendIntent(Context context, Intent fillInIntent) {
+ if (fillInIntent != null) {
+ fillInIntent.migrateExtraStreamToClipData();
+ fillInIntent.prepareToLeaveProcess();
+ }
+ try {
+ mIntentSender.sendIntent(context, 0, fillInIntent, null, null);
+ return true;
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "sendIntent " + this + " failed", e);
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ChooserTarget{" + mIntentSender.getCreatorPackage() + "'" + mTitle
+ + "', " + mScore + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeCharSequence(mTitle);
+ if (mIcon != null) {
+ dest.writeInt(1);
+ mIcon.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeFloat(mScore);
+ IntentSender.writeIntentSenderOrNullToParcel(mIntentSender, dest);
+ }
+
+ public static final Creator<ChooserTarget> CREATOR
+ = new Creator<ChooserTarget>() {
+ @Override
+ public ChooserTarget createFromParcel(Parcel source) {
+ return new ChooserTarget(source);
+ }
+
+ @Override
+ public ChooserTarget[] newArray(int size) {
+ return new ChooserTarget[size];
+ }
+ };
+}
diff --git a/core/java/android/service/chooser/ChooserTargetService.java b/core/java/android/service/chooser/ChooserTargetService.java
new file mode 100644
index 0000000..9188806
--- /dev/null
+++ b/core/java/android/service/chooser/ChooserTargetService.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 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.service.chooser;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * A service that receives calls from the system when the user is asked to choose
+ * a target for an intent explicitly by another app. The calling app must have invoked
+ * {@link android.content.Intent#ACTION_CHOOSER ACTION_CHOOSER} as handled by the system;
+ * applications do not have the ability to query a ChooserTargetService directly.
+ *
+ * <p>Which ChooserTargetServices are queried depends on a system-level policy decision
+ * made at the moment the chooser is invoked, including but not limited to user time
+ * spent with the app package or associated components in the foreground, recency of usage
+ * or frequency of usage. These will generally correlate with the order that app targets
+ * are shown in the list of intent handlers shown in the system chooser or resolver.</p>
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_CHOOSER_TARGET_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * <service android:name=".ChooserTargetService"
+ * android:label="@string/service_name"
+ * android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.chooser.ChooserTargetService" />
+ * </intent-filter>
+ * </service>
+ * </pre>
+ *
+ * <p>For the system to query your service, you must add a <meta-data> element to the
+ * Activity in your manifest that can handle Intents that you would also like to provide
+ * optional deep links for. For example, a chat app might offer deep links to recent active
+ * conversations instead of invoking a generic picker after the app itself is chosen as a target.
+ * </p>
+ *
+ * <p>The meta-data element should have the name
+ * <code>android.service.chooser.chooser_target_service</code> and a value corresponding to
+ * the component name of your service. Example:</p>
+ * <pre>
+ * <activity android:name=".MyShareActivity"
+ * android:label="@string/share_activity_label">
+ * <intent-filter>
+ * <action android:name="android.intent.action.SEND" />
+ * </intent-filter>
+ * <meta-data android:name="android.service.chooser.chooser_target_service"
+ * android:value=".ChooserTargetService" />
+ * </activity>
+ * </pre>
+ */
+public abstract class ChooserTargetService extends Service {
+ // TAG = "ChooserTargetService[MySubclass]";
+ private final String TAG = ChooserTargetService.class.getSimpleName()
+ + '[' + getClass().getSimpleName() + ']';
+
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.service.chooser.ChooserTargetService";
+
+ private IChooserTargetServiceWrapper mWrapper = null;
+
+ /**
+ * Called by the system to retrieve a set of deep-link {@link ChooserTarget targets} that
+ * can handle an intent.
+ *
+ * <p>The returned list should be sorted such that the most relevant targets appear first.
+ * Any PendingIntents used to construct the resulting ChooserTargets should always be prepared
+ * to have the relevant data fields filled in by the sender. See
+ * {@link ChooserTarget#ChooserTarget(CharSequence, android.graphics.Bitmap, float, android.app.PendingIntent) ChooserTarget}.</p>
+ *
+ * <p><em>Important:</em> Calls to this method from other applications will occur on
+ * a binder thread, not on your app's main thread. Make sure that access to relevant data
+ * within your app is thread-safe.</p>
+ *
+ * @param targetActivityName the ComponentName of the matched activity that referred the system
+ * to this ChooserTargetService
+ * @param matchedFilter the specific IntentFilter on the component that was matched
+ * @return a list of deep-link targets to fulfill the intent match, sorted by relevance
+ */
+ public abstract List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName,
+ IntentFilter matchedFilter);
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (!SERVICE_INTERFACE.equals(intent.getAction())) {
+ return null;
+ }
+
+ if (mWrapper == null) {
+ mWrapper = new IChooserTargetServiceWrapper();
+ }
+ return mWrapper;
+ }
+
+ private class IChooserTargetServiceWrapper extends IChooserTargetService.Stub {
+ @Override
+ public void getChooserTargets(ComponentName targetComponentName,
+ IntentFilter matchedFilter, IChooserTargetResult result) throws RemoteException {
+ List<ChooserTarget> targets = null;
+ try {
+ targets = onGetChooserTargets(targetComponentName, matchedFilter);
+ } finally {
+ result.sendResult(targets);
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/midi/IMidiListener.aidl b/core/java/android/service/chooser/IChooserTargetResult.aidl
similarity index 67%
copy from media/java/android/media/midi/IMidiListener.aidl
copy to core/java/android/service/chooser/IChooserTargetResult.aidl
index a4129e9..dbd7cbd 100644
--- a/media/java/android/media/midi/IMidiListener.aidl
+++ b/core/java/android/service/chooser/IChooserTargetResult.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 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.
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package android.media.midi;
+package android.service.chooser;
-import android.media.midi.MidiDeviceInfo;
+import android.service.chooser.ChooserTarget;
-/** @hide */
-oneway interface IMidiListener
+/**
+ * @hide
+ */
+interface IChooserTargetResult
{
- void onDeviceAdded(in MidiDeviceInfo device);
- void onDeviceRemoved(in MidiDeviceInfo device);
+ void sendResult(in List<ChooserTarget> targets);
}
diff --git a/core/java/android/service/chooser/IChooserTargetService.aidl b/core/java/android/service/chooser/IChooserTargetService.aidl
new file mode 100644
index 0000000..6cfa9a2
--- /dev/null
+++ b/core/java/android/service/chooser/IChooserTargetService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 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.service.chooser;
+
+import android.content.ComponentName;
+import android.content.IntentFilter;
+import android.service.chooser.IChooserTargetResult;
+
+/**
+ * @hide
+ */
+oneway interface IChooserTargetService
+{
+ void getChooserTargets(in ComponentName targetComponentName,
+ in IntentFilter matchedFilter, IChooserTargetResult result);
+}
\ No newline at end of file
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 77ef1da..1bdaef0 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -270,22 +270,30 @@
// generate new layout for affected text
StaticLayout reflowed;
+ StaticLayout.Builder b;
synchronized (sLock) {
reflowed = sStaticLayout;
+ b = sBuilder;
sStaticLayout = null;
+ sBuilder = null;
}
+ // TODO: make sure reflowed is properly initialized
if (reflowed == null) {
reflowed = new StaticLayout(null);
- } else {
- reflowed.prepare();
+ b = StaticLayout.Builder.obtain();
}
- reflowed.generate(text, where, where + after,
- getPaint(), getWidth(), getTextDirectionHeuristic(), getSpacingMultiplier(),
- getSpacingAdd(), false,
- true, mEllipsizedWidth, mEllipsizeAt);
+ b.setText(text, where, where + after)
+ .setPaint(getPaint())
+ .setWidth(getWidth())
+ .setTextDir(getTextDirectionHeuristic())
+ .setSpacingMult(getSpacingMultiplier())
+ .setSpacingAdd(getSpacingAdd())
+ .setEllipsizedWidth(mEllipsizedWidth)
+ .setEllipsize(mEllipsizeAt);
+ reflowed.generate(b, false, true);
int n = reflowed.getLineCount();
// If the new layout has a blank line at the end, but it is not
@@ -359,9 +367,10 @@
updateBlocks(startline, endline - 1, n);
+ b.finish();
synchronized (sLock) {
sStaticLayout = reflowed;
- reflowed.finish();
+ sBuilder = b;
}
}
@@ -720,7 +729,8 @@
private int mTopPadding, mBottomPadding;
- private static StaticLayout sStaticLayout = new StaticLayout(null);
+ private static StaticLayout sStaticLayout = null;
+ private static StaticLayout.Builder sBuilder = null;
private static final Object[] sLock = new Object[0];
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index e72e18f..832002c 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -66,21 +66,28 @@
}
static MeasuredText recycle(MeasuredText mt) {
- mt.mText = null;
- if (mt.mLen < 1000) {
- synchronized(sLock) {
- for (int i = 0; i < sCached.length; ++i) {
- if (sCached[i] == null) {
- sCached[i] = mt;
- mt.mText = null;
- break;
- }
+ mt.finish();
+ synchronized(sLock) {
+ for (int i = 0; i < sCached.length; ++i) {
+ if (sCached[i] == null) {
+ sCached[i] = mt;
+ mt.mText = null;
+ break;
}
}
}
return null;
}
+ void finish() {
+ mText = null;
+ if (mLen > 1000) {
+ mWidths = null;
+ mChars = null;
+ mLevels = null;
+ }
+ }
+
void setPos(int pos) {
mPos = pos - mTextStart;
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index ffb7d36..d1caa43 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -43,6 +43,153 @@
static final String TAG = "StaticLayout";
+ /**
+ * Builder for static layouts. It would be better if this were a public
+ * API (as it would offer much greater flexibility for adding new options)
+ * but for the time being it's just internal.
+ *
+ * @hide
+ */
+ public final static class Builder {
+ static Builder obtain() {
+ Builder b = null;
+ synchronized (sLock) {
+ for (int i = 0; i < sCached.length; i++) {
+ if (sCached[i] != null) {
+ b = sCached[i];
+ sCached[i] = null;
+ break;
+ }
+ }
+ }
+ if (b == null) {
+ b = new Builder();
+ }
+
+ // set default initial values
+ b.mWidth = 0;
+ b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
+ b.mSpacingMult = 1.0f;
+ b.mSpacingAdd = 0.0f;
+ b.mIncludePad = true;
+ b.mEllipsizedWidth = 0;
+ b.mEllipsize = null;
+ b.mMaxLines = Integer.MAX_VALUE;
+
+ b.mMeasuredText = MeasuredText.obtain();
+ return b;
+ }
+
+ static void recycle(Builder b) {
+ b.mPaint = null;
+ b.mText = null;
+ MeasuredText.recycle(b.mMeasuredText);
+ synchronized (sLock) {
+ for (int i = 0; i < sCached.length; i++) {
+ if (sCached[i] == null) {
+ sCached[i] = b;
+ break;
+ }
+ }
+ }
+ }
+
+ // release any expensive state
+ /* package */ void finish() {
+ mMeasuredText.finish();
+ }
+
+ public Builder setText(CharSequence source) {
+ return setText(source, 0, source.length());
+ }
+
+ public Builder setText(CharSequence source, int start, int end) {
+ mText = source;
+ mStart = start;
+ mEnd = end;
+ return this;
+ }
+
+ public Builder setPaint(TextPaint paint) {
+ mPaint = paint;
+ return this;
+ }
+
+ public Builder setWidth(int width) {
+ mWidth = width;
+ if (mEllipsize == null) {
+ mEllipsizedWidth = width;
+ }
+ return this;
+ }
+
+ public Builder setTextDir(TextDirectionHeuristic textDir) {
+ mTextDir = textDir;
+ return this;
+ }
+
+ // TODO: combine the following, as they're almost always set together?
+ public Builder setSpacingMult(float spacingMult) {
+ mSpacingMult = spacingMult;
+ return this;
+ }
+
+ public Builder setSpacingAdd(float spacingAdd) {
+ mSpacingAdd = spacingAdd;
+ return this;
+ }
+
+ public Builder setIncludePad(boolean includePad) {
+ mIncludePad = includePad;
+ return this;
+ }
+
+ // TODO: combine the following?
+ public Builder setEllipsizedWidth(int ellipsizedWidth) {
+ mEllipsizedWidth = ellipsizedWidth;
+ return this;
+ }
+
+ public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
+ mEllipsize = ellipsize;
+ return this;
+ }
+
+ public Builder setMaxLines(int maxLines) {
+ mMaxLines = maxLines;
+ return this;
+ }
+
+ public StaticLayout build() {
+ // TODO: can optimize based on whether ellipsis is needed
+ StaticLayout result = new StaticLayout(mText);
+ result.initFromBuilder(this);
+ recycle(this);
+ return result;
+ }
+
+ CharSequence mText;
+ int mStart;
+ int mEnd;
+ TextPaint mPaint;
+ int mWidth;
+ TextDirectionHeuristic mTextDir;
+ float mSpacingMult;
+ float mSpacingAdd;
+ boolean mIncludePad;
+ int mEllipsizedWidth;
+ TextUtils.TruncateAt mEllipsize;
+ int mMaxLines;
+
+ Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
+
+ // This will go away and be subsumed by native builder code
+ MeasuredText mMeasuredText;
+
+ private static final Object sLock = new Object();
+ private static final Builder[] sCached = new Builder[3];
+ }
+
public StaticLayout(CharSequence source, TextPaint paint,
int width,
Alignment align, float spacingmult, float spacingadd,
@@ -110,6 +257,17 @@
: new Ellipsizer(source),
paint, outerwidth, align, textDir, spacingmult, spacingadd);
+ Builder b = Builder.obtain();
+ b.setText(source, bufstart, bufend)
+ .setPaint(paint)
+ .setWidth(outerwidth)
+ .setTextDir(textDir)
+ .setSpacingMult(spacingmult)
+ .setSpacingAdd(spacingadd)
+ .setIncludePad(includepad)
+ .setEllipsizedWidth(ellipsizedWidth)
+ .setEllipsize(ellipsize)
+ .setMaxLines(maxLines);
/*
* This is annoying, but we can't refer to the layout until
* superclass construction is finished, and the superclass
@@ -136,14 +294,9 @@
mLines = new int[mLineDirections.length];
mMaximumVisibleLineCount = maxLines;
- mMeasured = MeasuredText.obtain();
+ initFromBuilder(b);
- generate(source, bufstart, bufend, paint, outerwidth, textDir, spacingmult,
- spacingadd, includepad, includepad, ellipsizedWidth,
- ellipsize);
-
- mMeasured = MeasuredText.recycle(mMeasured);
- mFontMetricsInt = null;
+ Builder.recycle(b);
}
/* package */ StaticLayout(CharSequence text) {
@@ -152,16 +305,23 @@
mColumns = COLUMNS_ELLIPSIZE;
mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
mLines = new int[mLineDirections.length];
- // FIXME This is never recycled
- mMeasured = MeasuredText.obtain();
}
- /* package */ void generate(CharSequence source, int bufStart, int bufEnd,
- TextPaint paint, int outerWidth,
- TextDirectionHeuristic textDir, float spacingmult,
- float spacingadd, boolean includepad,
- boolean trackpad, float ellipsizedWidth,
- TextUtils.TruncateAt ellipsize) {
+ private void initFromBuilder(Builder b) {
+ generate(b, b.mIncludePad, b.mIncludePad);
+ }
+
+ /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
+ CharSequence source = b.mText;
+ int bufStart = b.mStart;
+ int bufEnd = b.mEnd;
+ TextPaint paint = b.mPaint;
+ int outerWidth = b.mWidth;
+ TextDirectionHeuristic textDir = b.mTextDir;
+ float spacingmult = b.mSpacingMult;
+ float spacingadd = b.mSpacingAdd;
+ float ellipsizedWidth = b.mEllipsizedWidth;
+ TextUtils.TruncateAt ellipsize = b.mEllipsize;
LineBreaks lineBreaks = new LineBreaks();
// store span end locations
int[] spanEndCache = new int[4];
@@ -175,10 +335,10 @@
int v = 0;
boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
- Paint.FontMetricsInt fm = mFontMetricsInt;
+ Paint.FontMetricsInt fm = b.mFontMetricsInt;
int[] chooseHtv = null;
- MeasuredText measured = mMeasured;
+ MeasuredText measured = b.mMeasuredText;
Spanned spanned = null;
if (source instanceof Spanned)
@@ -746,14 +906,6 @@
return mEllipsizedWidth;
}
- void prepare() {
- mMeasured = MeasuredText.obtain();
- }
-
- void finish() {
- mMeasured = MeasuredText.recycle(mMeasured);
- }
-
// populates LineBreaks and returns the number of breaks found
//
// the arrays inside the LineBreaks objects are passed in as well
@@ -793,12 +945,6 @@
private static final double EXTRA_ROUNDING = 0.5;
- /*
- * This is reused across calls to generate()
- */
- private MeasuredText mMeasured;
- private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
-
// This is used to return three arrays from a single JNI call when
// performing line breaking
/*package*/ static class LineBreaks {
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index 68f725e..7da3941 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -475,6 +475,26 @@
}
/**
+ * Perform a {@link #remove(Object)} of all values in <var>array</var>
+ * @param array The array whose contents are to be removed.
+ */
+ public boolean removeAll(ArraySet<? extends E> array) {
+ // TODO: If array is sufficiently large, a marking approach might be beneficial. In a first
+ // pass, use the property that the sets are sorted by hash to make this linear passes
+ // (except for hash collisions, which means worst case still n*m), then do one
+ // collection pass into a new array. This avoids binary searches and excessive memcpy.
+ final int N = array.mSize;
+
+ // Note: ArraySet does not make thread-safety guarantees. So instead of OR-ing together all
+ // the single results, compare size before and after.
+ final int originalSize = mSize;
+ for (int i = 0; i < N; i++) {
+ remove(array.valueAt(i));
+ }
+ return originalSize != mSize;
+ }
+
+ /**
* Return the number of items in this array map.
*/
@Override
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f8e207f..5d519ed 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -217,6 +217,12 @@
WordIterator mWordIterator;
SpellChecker mSpellChecker;
+ // This word iterator is set with text and used to determine word boundaries
+ // when a user is selecting text.
+ private WordIterator mWordIteratorWithText;
+ // Indicate that the text in the word iterator needs to be updated.
+ private boolean mUpdateWordIteratorText;
+
private Rect mTempRect;
private TextView mTextView;
@@ -689,9 +695,52 @@
return mTextView.getTransformationMethod() instanceof PasswordTransformationMethod;
}
+ private int getWordStart(int offset) {
+ // FIXME - For this and similar methods we're not doing anything to check if there's
+ // a LocaleSpan in the text, this may be something we should try handling or checking for.
+ int retOffset = getWordIteratorWithText().getBeginning(offset);
+ if (retOffset == BreakIterator.DONE) retOffset = offset;
+ return retOffset;
+ }
+
+ private int getWordEnd(int offset, boolean includePunctuation) {
+ int retOffset = getWordIteratorWithText().getEnd(offset);
+ if (retOffset == BreakIterator.DONE) {
+ retOffset = offset;
+ } else if (includePunctuation) {
+ retOffset = handlePunctuation(retOffset);
+ }
+ return retOffset;
+ }
+
+ private boolean isEndBoundary(int offset) {
+ int thisEnd = getWordEnd(offset, false);
+ return offset == thisEnd;
+ }
+
+ private boolean isStartBoundary(int offset) {
+ int thisStart = getWordStart(offset);
+ return thisStart == offset;
+ }
+
+ private int handlePunctuation(int offset) {
+ // FIXME - Check with UX how repeated ending punctuation should be handled.
+ // FIXME - Check with UX if / how we would handle non sentence ending characters.
+ // FIXME - Consider punctuation in different languages.
+ CharSequence text = mTextView.getText();
+ if (offset < text.length()) {
+ int c = Character.codePointAt(text, offset);
+ if (c == 0x002e /* period */|| c == 0x003f /* question mark */
+ || c == 0x0021 /* exclamation mark */) {
+ offset = Character.offsetByCodePoints(text, offset, 1);
+ }
+ }
+ return offset;
+ }
+
/**
- * Adjusts selection to the word under last touch offset.
- * Return true if the operation was successfully performed.
+ * Adjusts selection to the word under last touch offset. Return true if the operation was
+ * successfully performed.
*/
private boolean selectCurrentWord() {
if (!canSelectText()) {
@@ -738,6 +787,8 @@
selectionStart = ((Spanned) mTextView.getText()).getSpanStart(urlSpan);
selectionEnd = ((Spanned) mTextView.getText()).getSpanEnd(urlSpan);
} else {
+ // FIXME - We should check if there's a LocaleSpan in the text, this may be
+ // something we should try handling or checking for.
final WordIterator wordIterator = getWordIterator();
wordIterator.setCharSequence(mTextView.getText(), minOffset, maxOffset);
@@ -760,6 +811,7 @@
void onLocaleChanged() {
// Will be re-created on demand in getWordIterator with the proper new locale
mWordIterator = null;
+ mWordIteratorWithText = null;
}
/**
@@ -772,6 +824,23 @@
return mWordIterator;
}
+ private WordIterator getWordIteratorWithText() {
+ if (mWordIteratorWithText == null) {
+ mWordIteratorWithText = new WordIterator(mTextView.getTextServicesLocale());
+ mUpdateWordIteratorText = true;
+ }
+ if (mUpdateWordIteratorText) {
+ // FIXME - Shouldn't copy all of the text as only the area of the text relevant
+ // to the user's selection is needed. A possible solution would be to
+ // copy some number N of characters near the selection and then when the
+ // user approaches N then we'd do another copy of the next N characters.
+ CharSequence text = mTextView.getText();
+ mWordIteratorWithText.setCharSequence(text, 0, text.length());
+ mUpdateWordIteratorText = false;
+ }
+ return mWordIteratorWithText;
+ }
+
private long getCharRange(int offset) {
final int textLength = mTextView.getText().length();
if (offset + 1 < textLength) {
@@ -920,9 +989,8 @@
mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
stopSelectionActionMode();
} else {
- getSelectionController().hide();
- selectCurrentWord();
- getSelectionController().show();
+ stopSelectionActionMode();
+ startSelectionActionMode();
}
handled = true;
}
@@ -1058,6 +1126,9 @@
void sendOnTextChanged(int start, int after) {
updateSpellCheckSpans(start, start + after, false);
+ // Flip flag to indicate the word iterator needs to have the text reset.
+ mUpdateWordIteratorText = true;
+
// Hide the controllers as soon as text is modified (typing, procedural...)
// We do not hide the span controllers, since they can be added when a new text is
// inserted into the text view (voice IME).
@@ -1613,6 +1684,9 @@
}
}
+ if (selectionStarted) {
+ getSelectionController().enterDrag();
+ }
return selectionStarted;
}
@@ -2894,7 +2968,6 @@
}
if (menu.hasVisibleItems() || mode.getCustomView() != null) {
- getSelectionController().show();
mTextView.setHasTransientState(true);
return true;
} else {
@@ -3232,6 +3305,8 @@
private Runnable mActionPopupShower;
// Minimum touch target size for handles
private int mMinSize;
+ // Indicates the line of text that the handle is on.
+ protected int mLine = -1;
public HandleView(Drawable drawableLtr, Drawable drawableRtl) {
super(mTextView.getContext());
@@ -3407,6 +3482,7 @@
addPositionToTouchUpFilter(offset);
}
final int line = layout.getLineForOffset(offset);
+ mLine = line;
mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX -
getHorizontalOffset() + getCursorOffset());
@@ -3456,6 +3532,30 @@
}
}
+ public void showAtLocation(int offset) {
+ // TODO - investigate if there's a better way to show the handles
+ // after the drag accelerator has occured.
+ int[] tmpCords = new int[2];
+ mTextView.getLocationInWindow(tmpCords);
+
+ Layout layout = mTextView.getLayout();
+ int posX = tmpCords[0];
+ int posY = tmpCords[1];
+
+ final int line = layout.getLineForOffset(offset);
+
+ int startX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f
+ - mHotspotX - getHorizontalOffset() + getCursorOffset());
+ int startY = layout.getLineBottom(line);
+
+ // Take TextView's padding and scroll into account.
+ startX += mTextView.viewportToContentHorizontalOffset();
+ startY += mTextView.viewportToContentVerticalOffset();
+
+ mContainer.showAtLocation(mTextView, Gravity.NO_GRAVITY,
+ startX + posX, startY + posY);
+ }
+
@Override
protected void onDraw(Canvas c) {
final int drawWidth = mDrawable.getIntrinsicWidth();
@@ -3694,6 +3794,12 @@
}
private class SelectionStartHandleView extends HandleView {
+ // The previous offset this handle was at.
+ private int mPrevOffset;
+ // Indicates whether the cursor is making adjustments within a word.
+ private boolean mInWord = false;
+ // Offset to track difference between touch and word boundary.
+ protected int mTouchWordOffset;
public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
super(drawableLtr, drawableRtl);
@@ -3701,11 +3807,7 @@
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
- return drawable.getIntrinsicWidth() / 4;
- } else {
- return (drawable.getIntrinsicWidth() * 3) / 4;
- }
+ return isRtlRun ? 0 : drawable.getIntrinsicWidth();
}
@Override
@@ -3727,21 +3829,81 @@
@Override
public void updatePosition(float x, float y) {
- int offset = mTextView.getOffsetForPosition(x, y);
+ final int trueOffset = mTextView.getOffsetForPosition(x, y);
+ final int currLine = mTextView.getLineAtCoordinate(y);
+ int offset = trueOffset;
+ boolean positionCursor = false;
- // Handles can not cross and selection is at least one character
- final int selectionEnd = mTextView.getSelectionEnd();
- if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1);
+ int end = getWordEnd(offset, true);
+ int start = getWordStart(offset);
- positionAtCursorOffset(offset, false);
+ if (offset < mPrevOffset) {
+ // User is increasing the selection.
+ if (!mInWord || currLine < mLine) {
+ // We're not in a word, or we're on a different line so we'll expand by
+ // word. First ensure the user has at least entered the next word.
+ int offsetToWord = Math.min((end - start) / 2, 2);
+ if (offset <= end - offsetToWord || currLine < mLine) {
+ offset = start;
+ } else {
+ offset = mPrevOffset;
+ }
+ }
+ mPrevOffset = offset;
+ mTouchWordOffset = trueOffset - offset;
+ mInWord = !isStartBoundary(offset);
+ positionCursor = true;
+ } else if (offset - mTouchWordOffset > mPrevOffset) {
+ // User is shrinking the selection.
+ if (currLine > mLine) {
+ // We're on a different line, so we'll snap to word boundaries.
+ offset = end;
+ }
+ offset -= mTouchWordOffset;
+ mPrevOffset = offset;
+ mInWord = !isEndBoundary(offset);
+ positionCursor = true;
+ }
+
+ // Handles can not cross and selection is at least one character.
+ if (positionCursor) {
+ final int selectionEnd = mTextView.getSelectionEnd();
+ if (offset >= selectionEnd) {
+ // We can't cross the handles so let's just constrain the Y value.
+ int alteredOffset = mTextView.getOffsetAtCoordinate(mLine, x);
+ if (alteredOffset >= selectionEnd) {
+ // Can't pass the other drag handle.
+ offset = Math.max(0, selectionEnd - 1);
+ } else {
+ offset = alteredOffset;
+ }
+ }
+ positionAtCursorOffset(offset, false);
+ }
}
public ActionPopupWindow getActionPopupWindow() {
return mActionPopupWindow;
}
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean superResult = super.onTouchEvent(event);
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ // Reset the touch word offset when the user has lifted their finger.
+ mTouchWordOffset = 0;
+ }
+ return superResult;
+ }
}
private class SelectionEndHandleView extends HandleView {
+ // The previous offset this handle was at.
+ private int mPrevOffset;
+ // Indicates whether the cursor is making adjustments within a word.
+ private boolean mInWord = false;
+ // Offset to track difference between touch and word boundary.
+ protected int mTouchWordOffset;
public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
super(drawableLtr, drawableRtl);
@@ -3749,11 +3911,7 @@
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
- return (drawable.getIntrinsicWidth() * 3) / 4;
- } else {
- return drawable.getIntrinsicWidth() / 4;
- }
+ return isRtlRun ? drawable.getIntrinsicWidth() : 0;
}
@Override
@@ -3775,20 +3933,72 @@
@Override
public void updatePosition(float x, float y) {
- int offset = mTextView.getOffsetForPosition(x, y);
+ final int trueOffset = mTextView.getOffsetForPosition(x, y);
+ final int currLine = mTextView.getLineAtCoordinate(y);
+ int offset = trueOffset;
+ boolean positionCursor = false;
- // Handles can not cross and selection is at least one character
- final int selectionStart = mTextView.getSelectionStart();
- if (offset <= selectionStart) {
- offset = Math.min(selectionStart + 1, mTextView.getText().length());
+ int end = getWordEnd(offset, true);
+ int start = getWordStart(offset);
+
+ if (offset > mPrevOffset) {
+ // User is increasing the selection.
+ if (!mInWord || currLine > mLine) {
+ // We're not in a word, or we're on a different line so we'll expand by
+ // word. First ensure the user has at least entered the next word.
+ int midPoint = Math.min((end - start) / 2, 2);
+ if (offset >= start + midPoint || currLine > mLine) {
+ offset = end;
+ } else {
+ offset = mPrevOffset;
+ }
+ }
+ mPrevOffset = offset;
+ mTouchWordOffset = offset - trueOffset;
+ mInWord = !isEndBoundary(offset);
+ positionCursor = true;
+ } else if (offset + mTouchWordOffset < mPrevOffset) {
+ // User is shrinking the selection.
+ if (currLine > mLine) {
+ // We're on a different line, so we'll snap to word boundaries.
+ offset = getWordStart(offset);
+ }
+ offset += mTouchWordOffset;
+ mPrevOffset = offset;
+ positionCursor = true;
+ mInWord = !isStartBoundary(offset);
}
- positionAtCursorOffset(offset, false);
+ if (positionCursor) {
+ final int selectionStart = mTextView.getSelectionStart();
+ if (offset <= selectionStart) {
+ // We can't cross the handles so let's just constrain the Y value.
+ int alteredOffset = mTextView.getOffsetAtCoordinate(mLine, x);
+ int length = mTextView.getText().length();
+ if (alteredOffset <= selectionStart) {
+ // Can't pass the other drag handle.
+ offset = Math.min(selectionStart + 1, length);
+ } else {
+ offset = Math.min(alteredOffset, length);
+ }
+ }
+ positionAtCursorOffset(offset, false);
+ }
}
public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
mActionPopupWindow = actionPopupWindow;
}
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean superResult = super.onTouchEvent(event);
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ // Reset the touch word offset when the user has lifted their finger.
+ mTouchWordOffset = 0;
+ }
+ return superResult;
+ }
}
/**
@@ -3871,6 +4081,11 @@
private float mDownPositionX, mDownPositionY;
private boolean mGestureStayedInTapRegion;
+ // Where the user first starts the drag motion.
+ private int mStartOffset = -1;
+ // Indicates whether the user is selecting text and using the drag accelerator.
+ private boolean mDragAcceleratorActive;
+
SelectionModifierCursorController() {
resetTouchOffsets();
}
@@ -3920,6 +4135,22 @@
if (mEndHandle != null) mEndHandle.hide();
}
+ public void enterDrag() {
+ // Just need to init the handles / hide insertion cursor.
+ show();
+ mDragAcceleratorActive = true;
+ // Start location of selection.
+ mStartOffset = mTextView.getOffsetForPosition(mLastDownPositionX,
+ mLastDownPositionY);
+ // Don't show the handles until user has lifted finger.
+ hide();
+
+ // This stops scrolling parents from intercepting the touch event, allowing
+ // the user to continue dragging across the screen to select text; TextView will
+ // scroll as necessary.
+ mTextView.getParent().requestDisallowInterceptTouchEvent(true);
+ }
+
public void onTouchEvent(MotionEvent event) {
// This is done even when the View does not have focus, so that long presses can start
// selection and tap can move cursor from this tap position.
@@ -3928,7 +4159,7 @@
final float x = event.getX();
final float y = event.getY();
- // Remember finger down position, to be able to start selection from there
+ // Remember finger down position, to be able to start selection from there.
mMinTouchOffset = mMaxTouchOffset = mTextView.getOffsetForPosition(x, y);
// Double tap detection
@@ -3967,23 +4198,112 @@
break;
case MotionEvent.ACTION_MOVE:
+ final ViewConfiguration viewConfiguration = ViewConfiguration.get(
+ mTextView.getContext());
+
if (mGestureStayedInTapRegion) {
final float deltaX = event.getX() - mDownPositionX;
final float deltaY = event.getY() - mDownPositionY;
final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
- final ViewConfiguration viewConfiguration = ViewConfiguration.get(
- mTextView.getContext());
int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop();
if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) {
mGestureStayedInTapRegion = false;
}
}
+
+ if (mStartHandle != null && mStartHandle.isShowing()) {
+ // Don't do the drag if the handles are showing already.
+ break;
+ }
+
+ if (mStartOffset != -1) {
+ final int rawOffset = mTextView.getOffsetForPosition(event.getX(),
+ event.getY());
+ int offset = rawOffset;
+
+ // We don't start "dragging" until the user is past the initial word that
+ // gets selected on long press.
+ int firstWordStart = getWordStart(mStartOffset);
+ int firstWordEnd = getWordEnd(mStartOffset, false);
+ if (offset > firstWordEnd || offset < firstWordStart) {
+
+ // Basically the goal in the below code is to have the highlight be
+ // offset so that your finger isn't covering the end point.
+ int fingerOffset = viewConfiguration.getScaledTouchSlop();
+ float mx = event.getX();
+ float my = event.getY();
+ if (mx > fingerOffset) mx -= fingerOffset;
+ if (my > fingerOffset) my -= fingerOffset;
+ offset = mTextView.getOffsetForPosition(mx, my);
+
+ // Perform the check for closeness at edge of view, if we're very close
+ // don't adjust the offset to be in front of the finger - otherwise the
+ // user can't select words at the edge.
+ if (mTextView.getWidth() - fingerOffset > mx) {
+ // We're going by word, so we need to make sure that the offset
+ // that we get is within this, so we'll get the previous boundary.
+ final WordIterator wordIterator = getWordIteratorWithText();
+
+ final int precedingOffset = wordIterator.preceding(offset);
+ if (mStartOffset < offset) {
+ // Expanding with bottom handle, in this case the selection end
+ // is before the finger.
+ offset = Math.max(precedingOffset - 1, 0);
+ } else {
+ // Expand with the start handle, in this case the selection
+ // start is before the finger.
+ if (precedingOffset == WordIterator.DONE) {
+ offset = 0;
+ } else {
+ offset = wordIterator.preceding(precedingOffset);
+ }
+ }
+ }
+ if (offset == WordIterator.DONE)
+ offset = rawOffset;
+
+ // Need to adjust start offset based on direction of movement.
+ int newStart = mStartOffset < offset ? getWordStart(mStartOffset)
+ : getWordEnd(mStartOffset, true);
+ Selection.setSelection((Spannable) mTextView.getText(), newStart,
+ offset);
+ }
+ }
break;
case MotionEvent.ACTION_UP:
mPreviousTapUpTime = SystemClock.uptimeMillis();
+ if (mDragAcceleratorActive) {
+ // No longer dragging to select text, let the parent intercept events.
+ mTextView.getParent().requestDisallowInterceptTouchEvent(false);
+
+ show();
+ int startOffset = mTextView.getSelectionStart();
+ int endOffset = mTextView.getSelectionEnd();
+
+ // Since we don't let drag handles pass once they're visible, we need to
+ // make sure the start / end locations are correct because the user *can*
+ // switch directions during the initial drag.
+ if (endOffset < startOffset) {
+ int tmp = endOffset;
+ endOffset = startOffset;
+ startOffset = tmp;
+
+ // Also update the selection with the right offsets in this case.
+ Selection.setSelection((Spannable) mTextView.getText(),
+ startOffset, endOffset);
+ }
+
+ // Need to do this to display the handles.
+ mStartHandle.showAtLocation(startOffset);
+ mEndHandle.showAtLocation(endOffset);
+
+ // No longer the first dragging motion, reset.
+ mDragAcceleratorActive = false;
+ mStartOffset = -1;
+ }
break;
}
}
@@ -4010,6 +4330,8 @@
public void resetTouchOffsets() {
mMinTouchOffset = mMaxTouchOffset = -1;
+ mStartOffset = -1;
+ mDragAcceleratorActive = false;
}
/**
@@ -4019,6 +4341,13 @@
return mStartHandle != null && mStartHandle.isDragging();
}
+ /**
+ * @return true if the user is selecting text using the drag accelerator.
+ */
+ public boolean isDragAcceleratorActive() {
+ return mDragAcceleratorActive;
+ }
+
public void onTouchModeChanged(boolean isInTouchMode) {
if (!isInTouchMode) {
hide();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 632f5c7..9bef1fe 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8098,7 +8098,14 @@
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
- if (mEditor != null) mEditor.onTouchEvent(event);
+ if (mEditor != null) {
+ mEditor.onTouchEvent(event);
+
+ if (mEditor.mSelectionModifierCursorController != null &&
+ mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
+ return true;
+ }
+ }
final boolean superResult = super.onTouchEvent(event);
@@ -9104,7 +9111,7 @@
return getLayout().getLineForVertical((int) y);
}
- private int getOffsetAtCoordinate(int line, float x) {
+ int getOffsetAtCoordinate(int line, float x) {
x = convertToLocalHorizontalCoordinate(x);
return getLayout().getOffsetForHorizontal(line, x);
}
@@ -9726,4 +9733,4 @@
TextView.this.spanChange(buf, what, s, -1, e, -1);
}
}
-}
\ No newline at end of file
+}
diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp
index 026cbee..d03bcf0 100644
--- a/core/jni/android/graphics/ColorFilter.cpp
+++ b/core/jni/android/graphics/ColorFilter.cpp
@@ -21,7 +21,7 @@
#include "SkColorFilter.h"
#include "SkColorMatrixFilter.h"
-#include "SkPorterDuff.h"
+#include "SkXfermode.h"
#include <Caches.h>
@@ -36,10 +36,9 @@
if (filter) SkSafeUnref(filter);
}
- static jlong CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
- jint modeHandle) {
- SkPorterDuff::Mode mode = (SkPorterDuff::Mode) modeHandle;
- return reinterpret_cast<jlong>(SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode)));
+ static jlong CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) {
+ SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(modeHandle);
+ return reinterpret_cast<jlong>(SkColorFilter::CreateModeFilter(srcColor, mode));
}
static jlong CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
diff --git a/core/jni/android/graphics/PorterDuff.cpp b/core/jni/android/graphics/PorterDuff.cpp
index 73b1691..d65864d 100644
--- a/core/jni/android/graphics/PorterDuff.cpp
+++ b/core/jni/android/graphics/PorterDuff.cpp
@@ -24,7 +24,7 @@
#include "GraphicsJNI.h"
#include "core_jni_helpers.h"
-#include "SkPorterDuff.h"
+#include "SkXfermode.h"
namespace android {
@@ -32,8 +32,28 @@
public:
static jlong CreateXfermode(JNIEnv* env, jobject, jint modeHandle) {
- SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle);
- return reinterpret_cast<jlong>(SkPorterDuff::CreateXfermode(mode));
+ // validate that the Java enum values match our expectations
+ SK_COMPILE_ASSERT(0 == SkXfermode::kClear_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(1 == SkXfermode::kSrc_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(2 == SkXfermode::kDst_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(3 == SkXfermode::kSrcOver_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(4 == SkXfermode::kDstOver_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(5 == SkXfermode::kSrcIn_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(6 == SkXfermode::kDstIn_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(7 == SkXfermode::kSrcOut_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(8 == SkXfermode::kDstOut_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(9 == SkXfermode::kSrcATop_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(10 == SkXfermode::kDstATop_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(11 == SkXfermode::kXor_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(16 == SkXfermode::kDarken_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(17 == SkXfermode::kLighten_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(13 == SkXfermode::kModulate_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(14 == SkXfermode::kScreen_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(12 == SkXfermode::kPlus_Mode, xfermode_mismatch);
+ SK_COMPILE_ASSERT(15 == SkXfermode::kOverlay_Mode, xfermode_mismatch);
+
+ SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(modeHandle);
+ return reinterpret_cast<jlong>(SkXfermode::Create(mode));
}
};
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 3f47a72..6591d29 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -3,7 +3,6 @@
#include "SkShader.h"
#include "SkGradientShader.h"
-#include "SkPorterDuff.h"
#include "SkComposeShader.h"
#include "SkTemplates.h"
#include "SkXfermode.h"
@@ -227,14 +226,13 @@
}
static jlong ComposeShader_create2(JNIEnv* env, jobject o,
- jlong shaderAHandle, jlong shaderBHandle, jint porterDuffModeHandle)
+ jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle)
{
SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
- SkPorterDuff::Mode porterDuffMode = static_cast<SkPorterDuff::Mode>(porterDuffModeHandle);
- SkAutoUnref au(SkPorterDuff::CreateXfermode(porterDuffMode));
- SkXfermode* mode = (SkXfermode*) au.get();
- SkShader* shader = new SkComposeShader(shaderA, shaderB, mode);
+ SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(xfermodeHandle);
+ SkAutoTUnref<SkXfermode> xfermode(SkXfermode::Create(mode));
+ SkShader* shader = new SkComposeShader(shaderA, shaderB, xfermode.get());
return reinterpret_cast<jlong>(shader);
}
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 7d5ca8d..f14e89e 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -186,8 +186,8 @@
}
static void drawColor(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) {
- SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle);
- get_canvas(canvasHandle)->drawColor(color, SkPorterDuff::ToXfermodeMode(mode));
+ SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(modeHandle);
+ get_canvas(canvasHandle)->drawColor(color, mode);
}
static void drawPaint(JNIEnv* env, jobject, jlong canvasHandle, jlong paintHandle) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b550558..2ce5bb3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2892,6 +2892,14 @@
android:description="@string/permdesc_bindNotificationListenerService"
android:protectionLevel="signature" />
+ <!-- Must be required by a {@link
+ android.service.chooser.ChooserTargetService}, to ensure that
+ only the system can bind to it. -->
+ <permission android:name="android.permission.BIND_CHOOSER_TARGET_SERVICE"
+ android:label="@string/permlab_bindChooserTargetService"
+ android:description="@string/permdesc_bindChooserTargetService"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Must be required by a {@link
android.service.notification.ConditionProviderService},
to ensure that only the system can bind to it.
diff --git a/core/res/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png
index 9cdc25b..1550b44 100644
--- a/core/res/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png
+++ b/core/res/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png
index 276d480..b309dfd 100644
--- a/core/res/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png
+++ b/core/res/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png
index 95c0168..b36a413 100644
--- a/core/res/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png
+++ b/core/res/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png
index 569332a..afd0bd2 100644
--- a/core/res/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png
+++ b/core/res/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png
index a01ac10..58f8c43 100644
--- a/core/res/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png
+++ b/core/res/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png
index d3602d9..42a893d 100644
--- a/core/res/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png
+++ b/core/res/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png
index 75085ce..d0f274a 100644
--- a/core/res/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png
+++ b/core/res/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png
index e2eb5be..f1f637a 100644
--- a/core/res/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png
+++ b/core/res/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/text_select_handle_left_mtrl_alpha.png b/core/res/res/drawable-xxxhdpi/text_select_handle_left_mtrl_alpha.png
new file mode 100644
index 0000000..643168f
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/text_select_handle_left_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/text_select_handle_right_mtrl_alpha.png b/core/res/res/drawable-xxxhdpi/text_select_handle_right_mtrl_alpha.png
new file mode 100644
index 0000000..e8f3aad
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/text_select_handle_right_mtrl_alpha.png
Binary files differ
diff --git a/core/res/res/values-watch/strings.xml b/core/res/res/values-watch/strings.xml
new file mode 100644
index 0000000..4ea2b52
--- /dev/null
+++ b/core/res/res/values-watch/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2015, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- [CHAR LIMIT=16] Message shown in upgrading dialog for each .apk that is optimized. -->
+ <!-- Each word has an 8 character limit due to screen width limitation. -->
+ <string name="android_upgrading_apk">App
+ <xliff:g id="number" example="123">%1$d</xliff:g> of
+ <xliff:g id="number" example="123">%2$d</xliff:g>.</string>
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index fd8b803..a383359 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -64,26 +64,11 @@
As of Honeycomb, blurring is not supported anymore. -->
<bool name="config_sf_slowBlur">true</bool>
- <!-- Flag indicating that the media framework should allow changing
- master volume stream and nothing else . -->
- <bool name="config_useMasterVolume">false</bool>
-
<!-- Flag indicating that the media framework should support playing of sounds on volume
key usage. This adds noticeable additional overhead to volume key processing, so
is disableable for products for which it is irrelevant. -->
<bool name="config_useVolumeKeySounds">true</bool>
- <!-- Array of integer pairs controlling the rate at which the master volume changes
- in response to volume up and down key events.
- The first integer of each pair is compared against the current master volume
- (in range 0 to 100).
- The last pair with first integer <= the current volume is chosen,
- and the second integer of the pair indicates the amount to increase the master volume
- when volume up is pressed. -->
- <integer-array name="config_masterVolumeRamp">
- <item>0</item> <item>5</item> <!-- default: always increase volume by 5% -->
- </integer-array>
-
<!-- The attenuation in dB applied to the sound effects played
through AudioManager.playSoundEffect() when no volume is specified. -->
<integer name="config_soundEffectVolumeDb">-6</integer>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7c745ac..67ce159 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2354,6 +2354,11 @@
<string name="permdesc_bindNotificationListenerService">Allows the holder to bind to the top-level interface of a notification listener service. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_bindChooserTargetService">bind to a chooser target service</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_bindChooserTargetService">Allows the holder to bind to the top-level interface of a chooser target service. Should never be needed for normal apps.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_bindConditionProviderService">bind to a condition provider service</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_bindConditionProviderService">Allows the holder to bind to the top-level interface of a condition provider service. Should never be needed for normal apps.</string>
@@ -5055,7 +5060,7 @@
<!-- Notify use that they are in Lock-to-app in accessibility mode -->
<string name="lock_to_app_toast_accessible">To unpin this screen, touch and hold Overview.</string>
<!-- Notify user that they are locked in lock-to-app mode -->
- <string name="lock_to_app_toast_locked">Screen is pinned. Unpinning isn\'t allowed by your organization.</string>
+ <string name="lock_to_app_toast_locked">App is pinned: Unpinning isn\'t allowed on this device.</string>
<!-- Starting lock-to-app indication. -->
<string name="lock_to_app_start">Screen pinned</string>
<!-- Exting lock-to-app indication. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e17f5fc..078c12f 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -274,7 +274,6 @@
<java-symbol type="bool" name="preferences_prefer_dual_pane" />
<java-symbol type="bool" name="skip_restoring_network_selection" />
<java-symbol type="bool" name="split_action_bar_is_narrow" />
- <java-symbol type="bool" name="config_useMasterVolume" />
<java-symbol type="bool" name="config_useVolumeKeySounds" />
<java-symbol type="bool" name="config_enableWallpaperService" />
<java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
@@ -1087,7 +1086,6 @@
<java-symbol type="array" name="sim_colors" />
<java-symbol type="array" name="special_locale_codes" />
<java-symbol type="array" name="special_locale_names" />
- <java-symbol type="array" name="config_masterVolumeRamp" />
<java-symbol type="array" name="config_cdma_dun_supported_types" />
<java-symbol type="array" name="config_disabledUntilUsedPreinstalledImes" />
<java-symbol type="array" name="config_operatorConsideredNonRoaming" />
diff --git a/docs/html/google/play-services/wallet.jd b/docs/html/google/play-services/wallet.jd
index e5ed514..3d0c64a 100644
--- a/docs/html/google/play-services/wallet.jd
+++ b/docs/html/google/play-services/wallet.jd
@@ -53,7 +53,7 @@
<p>Google Wallet provides auto-completion of addresses, minimizing user data entry. You can also
retrieve billing and shipping addresses directly from the user’s Wallet to-do form pre-fills.<br />
<a class="external-link"
- href="https://developers.google.com/commerce/wallet/instant-buy/android/reference/com/google/android/gms/wallet/MaskedWallet#getBillingAddress()">Get
+ href="{@docRoot}reference/com/google/android/gms/wallet/MaskedWallet#getBillingAddress()">Get
billing addresses</a>.</p>
</div>
diff --git a/docs/html/training/scheduling/index.jd b/docs/html/training/scheduling/index.jd
index 4d2db60..4239ffb 100644
--- a/docs/html/training/scheduling/index.jd
+++ b/docs/html/training/scheduling/index.jd
@@ -1,5 +1,5 @@
page.title=Managing Device Awake State
-page.tags=
+page.tags=wakelock, AlarmManager, WakefulBroadcastReceiver
trainingnavtop=true
startpage=true
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 91c8dba..681bc62 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -534,8 +534,9 @@
* @hide
*/
public long getNativeInstance() {
- if (mShader != null && mShader.getNativeInstance() != mNativeShader) {
- mNativeShader = mShader.getNativeInstance();
+ long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance();
+ if (newNativeShader != mNativeShader) {
+ mNativeShader = newNativeShader;
native_setShader(mNativePaint, mNativeShader);
}
return mNativePaint;
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index 2ef1662..f5fbe70 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -18,7 +18,7 @@
public class PorterDuff {
- // these value must match their native equivalents. See SkPorterDuff.h
+ // these value must match their native equivalents. See SkXfermode.h
public enum Mode {
/** [0, 0] */
CLEAR (0),
@@ -46,17 +46,17 @@
XOR (11),
/** [Sa + Da - Sa*Da,
Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
- DARKEN (12),
+ DARKEN (16),
/** [Sa + Da - Sa*Da,
Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
- LIGHTEN (13),
+ LIGHTEN (17),
/** [Sa * Da, Sc * Dc] */
- MULTIPLY (14),
+ MULTIPLY (24),
/** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
- SCREEN (15),
+ SCREEN (14),
/** Saturate(S + D) */
- ADD (16),
- OVERLAY (17);
+ ADD (12),
+ OVERLAY (15);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 71c1fc3..d313c18 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -216,7 +216,7 @@
virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
const float* verts, const float* tex, const int* colors,
const uint16_t* indices, int indexCount, const SkPaint& paint) override
- { LOG_ALWAYS_FATAL("DisplayListRenderer does not support drawVertices()"); }
+ { /* DisplayListRenderer does not support drawVertices(); ignore */ }
// Bitmap-based
virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override;
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index f94dc38..711b11c 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -198,7 +198,7 @@
mOutGlop->mesh.vertices = {
mCaches.patchCache.getMeshBuffer(),
VertexAttribFlags::kTextureCoord,
- (void*)patch.offset, (void*)patch.textureOffset, nullptr,
+ (void*)patch.positionOffset, (void*)patch.textureOffset, nullptr,
kTextureVertexStride };
mOutGlop->mesh.elementCount = patch.indexCount;
return *this;
@@ -518,6 +518,7 @@
}
mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f);
+ mOutGlop->bounds = source;
mOutGlop->bounds.translate(offsetX, offsetY);
return *this;
}
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index eef4b73..7fd115f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2384,7 +2384,7 @@
ignoreTransform = true;
}
drawIndexedTextureMesh(left, top, right, bottom, texture->id, paint,
- texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset,
+ texture->blend, (GLvoid*) mesh->positionOffset, (GLvoid*) mesh->textureOffset,
GL_TRIANGLES, mesh->indexCount, false, ignoreTransform,
mCaches.patchCache.getMeshBuffer(), kModelViewMode_Translate, !mesh->hasEmptyQuads);
diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp
index ce51e04..f673c6a 100644
--- a/libs/hwui/Patch.cpp
+++ b/libs/hwui/Patch.cpp
@@ -24,6 +24,7 @@
#include "Patch.h"
#include "Properties.h"
#include "UvMapper.h"
+#include "utils/MathUtils.h"
namespace android {
namespace uirenderer {
@@ -36,19 +37,11 @@
return verticesCount * sizeof(TextureVertex);
}
-TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight,
- float width, float height, const Res_png_9patch* patch) {
- UvMapper mapper;
- return createMesh(bitmapWidth, bitmapHeight, width, height, mapper, patch);
-}
-
-TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight,
- float width, float height, const UvMapper& mapper, const Res_png_9patch* patch) {
- if (vertices) return vertices.get();
+Patch::Patch(const float bitmapWidth, const float bitmapHeight,
+ float width, float height, const UvMapper& mapper, const Res_png_9patch* patch)
+ : mColors(patch->getColors()) {
int8_t emptyQuads = 0;
- mColors = patch->getColors();
-
const int8_t numColors = patch->numColors;
if (uint8_t(numColors) < sizeof(uint32_t) * 4) {
for (int8_t i = 0; i < numColors; i++) {
@@ -64,7 +57,7 @@
uint32_t yCount = patch->numYDivs;
uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 4;
- if (maxVertices == 0) return nullptr;
+ if (maxVertices == 0) return;
vertices.reset(new TextureVertex[maxVertices]);
TextureVertex* vertex = vertices.get();
@@ -151,8 +144,6 @@
memcpy(reducedVertices.get(), vertices.get(), verticesCount * sizeof(TextureVertex));
vertices = std::move(reducedVertices);
}
-
- return vertices.get();
}
void Patch::generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex,
@@ -200,10 +191,10 @@
const uint32_t oldQuadCount = quadCount;
quadCount++;
- if (x1 < 0.0f) x1 = 0.0f;
- if (x2 < 0.0f) x2 = 0.0f;
- if (y1 < 0.0f) y1 = 0.0f;
- if (y2 < 0.0f) y2 = 0.0f;
+ x1 = MathUtils::max(x1, 0.0f);
+ x2 = MathUtils::max(x2, 0.0f);
+ y1 = MathUtils::max(y1, 0.0f);
+ y2 = MathUtils::max(y2, 0.0f);
// Skip degenerate and transparent (empty) quads
if ((mColors[oldQuadCount] == 0) || x1 >= x2 || y1 >= y2) {
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
index 9afb41d..b63bd24 100644
--- a/libs/hwui/Patch.h
+++ b/libs/hwui/Patch.h
@@ -39,7 +39,9 @@
class Patch {
public:
- Patch() {}
+ Patch(const float bitmapWidth, const float bitmapHeight,
+ float width, float height,
+ const UvMapper& mapper, const Res_png_9patch* patch);
/**
* Returns the size of this patch's mesh in bytes.
@@ -52,15 +54,9 @@
bool hasEmptyQuads = false;
Vector<Rect> quads;
- Rect bounds;
- GLintptr offset = 0;
+ GLintptr positionOffset = 0;
GLintptr textureOffset = 0;
- TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight,
- float width, float height, const Res_png_9patch* patch);
- TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight,
- float width, float height, const UvMapper& mapper, const Res_png_9patch* patch);
-
private:
void generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex,
float y1, float y2, float v1, float v2, float stretchX, float rescaleX,
@@ -68,7 +64,7 @@
void generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, float y2,
float u1, float v1, float u2, float v2, uint32_t& quadCount);
- const uint32_t* mColors = nullptr;
+ const uint32_t* mColors;
UvMapper mUvMapper;
}; // struct Patch
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index af403b4..2765262 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -162,7 +162,7 @@
// Release the patch and mark the space in the free list
Patch* patch = pair.getSecond();
- BufferBlock* block = new BufferBlock(patch->offset, patch->getSize());
+ BufferBlock* block = new BufferBlock(patch->positionOffset, patch->getSize());
block->next = mFreeBlocks;
mFreeBlocks = block;
@@ -190,7 +190,7 @@
* Sets the mesh's offsets and copies its associated vertices into
* the mesh buffer (VBO).
*/
-void PatchCache::setupMesh(Patch* newMesh, TextureVertex* vertices) {
+void PatchCache::setupMesh(Patch* newMesh) {
// This call ensures the VBO exists and that it is bound
init();
@@ -223,9 +223,9 @@
}
// Copy the 9patch mesh in the VBO
- newMesh->offset = (GLintptr) (block->offset);
- newMesh->textureOffset = newMesh->offset + kMeshTextureOffset;
- glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices);
+ newMesh->positionOffset = (GLintptr) (block->offset);
+ newMesh->textureOffset = newMesh->positionOffset + kMeshTextureOffset;
+ glBufferSubData(GL_ARRAY_BUFFER, newMesh->positionOffset, size, newMesh->vertices.get());
// Remove the block since we've used it entirely
if (block->size == size) {
@@ -244,6 +244,8 @@
mSize += size;
}
+static const UvMapper sIdentity;
+
const Patch* PatchCache::get(const AssetAtlas::Entry* entry,
const uint32_t bitmapWidth, const uint32_t bitmapHeight,
const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) {
@@ -252,20 +254,12 @@
const Patch* mesh = mCache.get(description);
if (!mesh) {
- Patch* newMesh = new Patch();
- TextureVertex* vertices;
+ const UvMapper& mapper = entry ? entry->uvMapper : sIdentity;
+ Patch* newMesh = new Patch(bitmapWidth, bitmapHeight,
+ pixelWidth, pixelHeight, mapper, patch);
- if (entry) {
- // An atlas entry has a UV mapper
- vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
- pixelWidth, pixelHeight, entry->uvMapper, patch);
- } else {
- vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
- pixelWidth, pixelHeight, patch);
- }
-
- if (vertices) {
- setupMesh(newMesh, vertices);
+ if (newMesh->vertices) {
+ setupMesh(newMesh);
}
#if DEBUG_PATCHES
@@ -284,7 +278,7 @@
String8 dump;
BufferBlock* block = mFreeBlocks;
while (block) {
- dump.appendFormat("->(%d, %d)", block->offset, block->size);
+ dump.appendFormat("->(%d, %d)", block->positionOffset, block->size);
block = block->next;
}
ALOGD("%s: Free blocks%s", prefix, dump.string());
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
index e038720..387f79a 100644
--- a/libs/hwui/PatchCache.h
+++ b/libs/hwui/PatchCache.h
@@ -160,7 +160,7 @@
void clearCache();
void createVertexBuffer();
- void setupMesh(Patch* newMesh, TextureVertex* vertices);
+ void setupMesh(Patch* newMesh);
void remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch);
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 1716cf0..c82082f 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -252,6 +252,13 @@
bottom = fmaxf(bottom, y);
}
+ void expandToCoverRect(float otherLeft, float otherTop, float otherRight, float otherBottom) {
+ left = fminf(left, otherLeft);
+ top = fminf(top, otherTop);
+ right = fmaxf(right, otherRight);
+ bottom = fmaxf(bottom, otherBottom);
+ }
+
SkRect toSkRect() const {
return SkRect::MakeLTRB(left, top, right, bottom);
}
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index 3c65705..ec1bb90 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -22,9 +22,10 @@
namespace android {
namespace uirenderer {
-SkiaCanvasProxy::SkiaCanvasProxy(Canvas* canvas)
+SkiaCanvasProxy::SkiaCanvasProxy(Canvas* canvas, bool filterHwuiCalls)
: INHERITED(canvas->width(), canvas->height())
- , mCanvas(canvas) {}
+ , mCanvas(canvas)
+ , mFilterHwuiCalls(filterHwuiCalls) {}
void SkiaCanvasProxy::onDrawPaint(const SkPaint& paint) {
mCanvas->drawPaint(paint);
@@ -32,6 +33,10 @@
void SkiaCanvasProxy::onDrawPoints(PointMode pointMode, size_t count, const SkPoint pts[],
const SkPaint& paint) {
+ if (!pts || count == 0) {
+ return;
+ }
+
// convert the SkPoints into floats
SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats);
const size_t floatCount = count << 1;
@@ -118,6 +123,9 @@
void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkPoint vertices[],
const SkPoint texs[], const SkColor colors[], SkXfermode*, const uint16_t indices[],
int indexCount, const SkPaint& paint) {
+ if (mFilterHwuiCalls) {
+ return;
+ }
// convert the SkPoints into floats
SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats);
const int floatCount = vertexCount << 1;
@@ -312,6 +320,9 @@
void SkiaCanvasProxy::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
const SkPoint texCoords[4], SkXfermode* xmode, const SkPaint& paint) {
+ if (mFilterHwuiCalls) {
+ return;
+ }
SkPatchUtils::VertexData data;
SkMatrix matrix;
diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h
index 4322fcf..0de9650 100644
--- a/libs/hwui/SkiaCanvasProxy.h
+++ b/libs/hwui/SkiaCanvasProxy.h
@@ -27,16 +27,19 @@
/**
* This class serves as a proxy between Skia's SkCanvas and Android Framework's
- * Canvas. The class does not maintain any state and will pass through any request
- * directly to the Canvas provided in the constructor.
+ * Canvas. The class does not maintain any draw-related state and will pass
+ * through most requests directly to the Canvas provided in the constructor.
*
* Upon construction it is expected that the provided Canvas has already been
* prepared for recording and will continue to be in the recording state while
* this proxy class is being used.
+ *
+ * If filterHwuiCalls is true, the proxy silently ignores away draw calls that
+ * aren't supported by HWUI.
*/
class ANDROID_API SkiaCanvasProxy : public SkCanvas {
public:
- SkiaCanvasProxy(Canvas* canvas);
+ SkiaCanvasProxy(Canvas* canvas, bool filterHwuiCalls = false);
virtual ~SkiaCanvasProxy() {}
protected:
@@ -94,6 +97,7 @@
private:
Canvas* mCanvas;
+ bool mFilterHwuiCalls;
typedef SkCanvas INHERITED;
};
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3a07bef..287b646 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -26,7 +26,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.media.RemoteController.OnClientUpdateListener;
import android.media.audiopolicy.AudioPolicy;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -59,10 +58,8 @@
private final Context mContext;
private long mVolumeKeyUpTime;
- private final boolean mUseMasterVolume;
private final boolean mUseVolumeKeySounds;
private final boolean mUseFixedVolume;
- private final Binder mToken = new Binder();
private static String TAG = "AudioManager";
private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
@@ -148,17 +145,6 @@
"android.media.STREAM_MUTE_CHANGED_ACTION";
/**
- * @hide Broadcast intent when the master volume changes.
- * Includes the new volume
- *
- * @see #EXTRA_MASTER_VOLUME_VALUE
- * @see #EXTRA_PREV_MASTER_VOLUME_VALUE
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String MASTER_VOLUME_CHANGED_ACTION =
- "android.media.MASTER_VOLUME_CHANGED_ACTION";
-
- /**
* @hide Broadcast intent when the master mute state changes.
* Includes the the new volume
*
@@ -210,20 +196,6 @@
"android.media.EXTRA_PREV_VOLUME_STREAM_VALUE";
/**
- * @hide The new master volume value for the master volume changed intent.
- * Value is integer between 0 and 100 inclusive.
- */
- public static final String EXTRA_MASTER_VOLUME_VALUE =
- "android.media.EXTRA_MASTER_VOLUME_VALUE";
-
- /**
- * @hide The previous master volume value for the master volume changed intent.
- * Value is integer between 0 and 100 inclusive.
- */
- public static final String EXTRA_PREV_MASTER_VOLUME_VALUE =
- "android.media.EXTRA_PREV_MASTER_VOLUME_VALUE";
-
- /**
* @hide The new master volume mute state for the master mute changed intent.
* Value is boolean
*/
@@ -604,8 +576,6 @@
*/
public AudioManager(Context context) {
mContext = context;
- mUseMasterVolume = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
mUseVolumeKeySounds = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useVolumeKeySounds);
mUseFixedVolume = mContext.getResources().getBoolean(
@@ -667,12 +637,8 @@
* The user has hit another key during the delay (e.g., 300ms)
* since the last volume key up, so cancel any sounds.
*/
- if (mUseMasterVolume) {
- adjustMasterVolume(ADJUST_SAME, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
- } else {
- adjustSuggestedStreamVolume(ADJUST_SAME,
- stream, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
- }
+ adjustSuggestedStreamVolume(ADJUST_SAME,
+ stream, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
}
@@ -688,22 +654,12 @@
* Adjust the volume in on key down since it is more
* responsive to the user.
*/
- int flags = FLAG_SHOW_UI | FLAG_VIBRATE;
-
- if (mUseMasterVolume) {
- adjustMasterVolume(
- keyCode == KeyEvent.KEYCODE_VOLUME_UP
- ? ADJUST_RAISE
- : ADJUST_LOWER,
- flags);
- } else {
- adjustSuggestedStreamVolume(
- keyCode == KeyEvent.KEYCODE_VOLUME_UP
- ? ADJUST_RAISE
- : ADJUST_LOWER,
- stream,
- flags);
- }
+ adjustSuggestedStreamVolume(
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ ? ADJUST_RAISE
+ : ADJUST_LOWER,
+ stream,
+ FLAG_SHOW_UI | FLAG_VIBRATE);
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
if (event.getRepeatCount() == 0) {
@@ -726,15 +682,10 @@
* sound to play when a user holds down volume down to mute.
*/
if (mUseVolumeKeySounds) {
- if (mUseMasterVolume) {
- adjustMasterVolume(ADJUST_SAME, FLAG_PLAY_SOUND);
- } else {
- int flags = FLAG_PLAY_SOUND;
- adjustSuggestedStreamVolume(
- ADJUST_SAME,
- stream,
- flags);
- }
+ adjustSuggestedStreamVolume(
+ ADJUST_SAME,
+ stream,
+ FLAG_PLAY_SOUND);
}
mVolumeKeyUpTime = SystemClock.uptimeMillis();
break;
@@ -783,12 +734,8 @@
public void adjustStreamVolume(int streamType, int direction, int flags) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
- } else {
- service.adjustStreamVolume(streamType, direction, flags,
- mContext.getOpPackageName());
- }
+ service.adjustStreamVolume(streamType, direction, flags,
+ mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustStreamVolume", e);
}
@@ -818,17 +765,8 @@
* @see #isVolumeFixed()
*/
public void adjustVolume(int direction, int flags) {
- IAudioService service = getService();
- try {
- if (mUseMasterVolume) {
- service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
- } else {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
- helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in adjustVolume", e);
- }
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+ helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
}
/**
@@ -856,34 +794,17 @@
* @see #isVolumeFixed()
*/
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
- IAudioService service = getService();
- try {
- if (mUseMasterVolume) {
- service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
- } else {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
- helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e);
- }
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+ helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
}
- /**
- * Adjusts the master volume for the device's audio amplifier.
- * <p>
- *
- * @param steps The number of volume steps to adjust. A positive
- * value will raise the volume.
- * @param flags One or more flags.
- * @hide
- */
- public void adjustMasterVolume(int steps, int flags) {
+ /** @hide */
+ public void setMasterMute(boolean mute, int flags) {
IAudioService service = getService();
try {
- service.adjustMasterVolume(steps, flags, mContext.getOpPackageName());
+ service.setMasterMute(mute, flags, mContext.getOpPackageName());
} catch (RemoteException e) {
- Log.e(TAG, "Dead object in adjustMasterVolume", e);
+ Log.e(TAG, "Dead object in setMasterMute", e);
}
}
@@ -935,11 +856,7 @@
public int getStreamMaxVolume(int streamType) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- return service.getMasterMaxVolume();
- } else {
- return service.getStreamMaxVolume(streamType);
- }
+ return service.getStreamMaxVolume(streamType);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getStreamMaxVolume", e);
return 0;
@@ -957,11 +874,7 @@
public int getStreamVolume(int streamType) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- return service.getMasterVolume();
- } else {
- return service.getStreamVolume(streamType);
- }
+ return service.getStreamVolume(streamType);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getStreamVolume", e);
return 0;
@@ -976,11 +889,7 @@
public int getLastAudibleStreamVolume(int streamType) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- return service.getLastAudibleMasterVolume();
- } else {
- return service.getLastAudibleStreamVolume(streamType);
- }
+ return service.getLastAudibleStreamVolume(streamType);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in getLastAudibleStreamVolume", e);
return 0;
@@ -993,12 +902,12 @@
* It is assumed that this stream type is also tied to ringer mode changes.
* @hide
*/
- public int getMasterStreamType() {
+ public int getUiSoundsStreamType() {
IAudioService service = getService();
try {
- return service.getMasterStreamType();
+ return service.getUiSoundsStreamType();
} catch (RemoteException e) {
- Log.e(TAG, "Dead object in getMasterStreamType", e);
+ Log.e(TAG, "Dead object in getUiSoundsStreamType", e);
return STREAM_RING;
}
}
@@ -1043,82 +952,13 @@
public void setStreamVolume(int streamType, int index, int flags) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- service.setMasterVolume(index, flags, mContext.getOpPackageName());
- } else {
- service.setStreamVolume(streamType, index, flags, mContext.getOpPackageName());
- }
+ service.setStreamVolume(streamType, index, flags, mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setStreamVolume", e);
}
}
/**
- * Returns the maximum volume index for master volume.
- *
- * @hide
- */
- public int getMasterMaxVolume() {
- IAudioService service = getService();
- try {
- return service.getMasterMaxVolume();
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in getMasterMaxVolume", e);
- return 0;
- }
- }
-
- /**
- * Returns the current volume index for master volume.
- *
- * @return The current volume index for master volume.
- * @hide
- */
- public int getMasterVolume() {
- IAudioService service = getService();
- try {
- return service.getMasterVolume();
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in getMasterVolume", e);
- return 0;
- }
- }
-
- /**
- * Get last audible volume before master volume was muted.
- *
- * @hide
- */
- public int getLastAudibleMasterVolume() {
- IAudioService service = getService();
- try {
- return service.getLastAudibleMasterVolume();
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in getLastAudibleMasterVolume", e);
- return 0;
- }
- }
-
- /**
- * Sets the volume index for master volume.
- *
- * @param index The volume index to set. See
- * {@link #getMasterMaxVolume()} for the largest valid value.
- * @param flags One or more flags.
- * @see #getMasterMaxVolume()
- * @see #getMasterVolume()
- * @hide
- */
- public void setMasterVolume(int index, int flags) {
- IAudioService service = getService();
- try {
- service.setMasterVolume(index, flags, mContext.getOpPackageName());
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in setMasterVolume", e);
- }
- }
-
- /**
* Solo or unsolo a particular stream.
* <p>
* Do not use. This method has been deprecated and is now a no-op.
@@ -1189,11 +1029,7 @@
public boolean isStreamMute(int streamType) {
IAudioService service = getService();
try {
- if (mUseMasterVolume) {
- return service.isMasterMute();
- } else {
- return service.isStreamMute(streamType);
- }
+ return service.isStreamMute(streamType);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in isStreamMute", e);
return false;
@@ -1223,9 +1059,6 @@
* @hide
*/
public void forceVolumeControlStream(int streamType) {
- if (mUseMasterVolume) {
- return;
- }
IAudioService service = getService();
try {
service.forceVolumeControlStream(streamType, mICallBack);
@@ -2694,7 +2527,7 @@
* metadata updates and playback state information from applications using
* {@link RemoteControlClient}, and control their playback.
* <p>
- * Registration requires the {@link OnClientUpdateListener} listener to be
+ * Registration requires the {@link RemoteController.OnClientUpdateListener} listener to be
* one of the enabled notification listeners (see
* {@link android.service.notification.NotificationListenerService}).
*
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index ef5710c..059d940 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -36,9 +36,6 @@
public abstract void setStreamVolumeForUid(int streamType, int direction, int flags,
String callingPackage, int uid);
- public abstract void adjustMasterVolumeForUid(int steps, int flags, String callingPackage,
- int uid);
-
public abstract void setRingerModeDelegate(RingerModeDelegate delegate);
public abstract int getRingerModeInternal();
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 17f5b59..20f7d29 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -44,32 +44,24 @@
void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage);
- void adjustMasterVolume(int direction, int flags, String callingPackage);
-
void setStreamVolume(int streamType, int index, int flags, String callingPackage);
oneway void setRemoteStreamVolume(int index);
- void setMasterVolume(int index, int flags, String callingPackage);
-
boolean isStreamMute(int streamType);
void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb);
boolean isMasterMute();
- int getStreamVolume(int streamType);
+ void setMasterMute(boolean mute, int flags, String callingPackage);
- int getMasterVolume();
+ int getStreamVolume(int streamType);
int getStreamMaxVolume(int streamType);
- int getMasterMaxVolume();
-
int getLastAudibleStreamVolume(int streamType);
- int getLastAudibleMasterVolume();
-
void setMicrophoneMute(boolean on, String callingPackage);
void setRingerModeExternal(int ringerMode, String caller);
@@ -187,7 +179,7 @@
void setRingtonePlayer(IRingtonePlayer player);
IRingtonePlayer getRingtonePlayer();
- int getMasterStreamType();
+ int getUiSoundsStreamType();
void setWiredDeviceConnectionState(int type, int state, String address, String name);
int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
diff --git a/media/java/android/media/IVolumeController.aidl b/media/java/android/media/IVolumeController.aidl
index e3593a6..90ac416 100644
--- a/media/java/android/media/IVolumeController.aidl
+++ b/media/java/android/media/IVolumeController.aidl
@@ -27,8 +27,6 @@
void volumeChanged(int streamType, int flags);
- void masterVolumeChanged(int flags);
-
void masterMuteChanged(int flags);
void setLayoutDirection(int layoutDirection);
diff --git a/media/java/android/media/midi/IMidiListener.aidl b/media/java/android/media/midi/IMidiDeviceListener.aidl
similarity index 95%
rename from media/java/android/media/midi/IMidiListener.aidl
rename to media/java/android/media/midi/IMidiDeviceListener.aidl
index a4129e9..17d9bfd 100644
--- a/media/java/android/media/midi/IMidiListener.aidl
+++ b/media/java/android/media/midi/IMidiDeviceListener.aidl
@@ -19,7 +19,7 @@
import android.media.midi.MidiDeviceInfo;
/** @hide */
-oneway interface IMidiListener
+oneway interface IMidiDeviceListener
{
void onDeviceAdded(in MidiDeviceInfo device);
void onDeviceRemoved(in MidiDeviceInfo device);
diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl
index bba35f5..617b03e 100644
--- a/media/java/android/media/midi/IMidiManager.aidl
+++ b/media/java/android/media/midi/IMidiManager.aidl
@@ -16,8 +16,8 @@
package android.media.midi;
+import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceServer;
-import android.media.midi.IMidiListener;
import android.media.midi.MidiDeviceInfo;
import android.os.Bundle;
import android.os.IBinder;
@@ -28,14 +28,20 @@
MidiDeviceInfo[] getDeviceList();
// for device creation & removal notifications
- void registerListener(IBinder token, in IMidiListener listener);
- void unregisterListener(IBinder token, in IMidiListener listener);
+ void registerListener(IBinder token, in IMidiDeviceListener listener);
+ void unregisterListener(IBinder token, in IMidiDeviceListener listener);
- // for communicating with MIDI devices
+ // for opening built-in MIDI devices
IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device);
- // for implementing virtual MIDI devices
+ // for registering built-in MIDI devices
MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
- int numOutputPorts, in Bundle properties, boolean isPrivate, int type);
+ int numOutputPorts, in Bundle properties, int type);
+
+ // for unregistering built-in MIDI devices
void unregisterDeviceServer(in IMidiDeviceServer server);
+
+ // used by MidiDeviceService to access the MidiDeviceInfo that was created based on its
+ // manifest's meta-data
+ MidiDeviceInfo getServiceDeviceInfo(String packageName, String className);
}
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index fd35052..b7756fd 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -50,6 +50,7 @@
private final int mInputPortCount;
private final int mOutputPortCount;
private final Bundle mProperties;
+ private final boolean mIsPrivate;
/**
* Bundle key for the device's manufacturer name property.
@@ -83,6 +84,8 @@
* Bundle key for the device's ALSA card number.
* Only set for USB MIDI devices.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ *
+ * @hide
*/
public static final String PROPERTY_ALSA_CARD = "alsa_card";
@@ -90,20 +93,32 @@
* Bundle key for the device's ALSA device number.
* Only set for USB MIDI devices.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ *
+ * @hide
*/
public static final String PROPERTY_ALSA_DEVICE = "alsa_device";
/**
+ * {@link android.content.pm.ServiceInfo} for the service hosting the device implementation.
+ * Only set for Virtual MIDI devices.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ *
+ * @hide
+ */
+ public static final String PROPERTY_SERVICE_INFO = "service_info";
+
+ /**
* MidiDeviceInfo should only be instantiated by MidiService implementation
* @hide
*/
public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
- Bundle properties) {
+ Bundle properties, boolean isPrivate) {
mType = type;
mId = id;
mInputPortCount = numInputPorts;
mOutputPortCount = numOutputPorts;
mProperties = properties;
+ mIsPrivate = isPrivate;
}
/**
@@ -152,6 +167,16 @@
return mProperties;
}
+ /**
+ * Returns true if the device is private. Private devices are only visible and accessible
+ * to clients with the same UID as the application that is hosting the device.
+ *
+ * @return true if the device is private
+ */
+ public boolean isPrivate() {
+ return mIsPrivate;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof MidiDeviceInfo) {
@@ -171,7 +196,8 @@
return ("MidiDeviceInfo[mType=" + mType +
",mInputPortCount=" + mInputPortCount +
",mOutputPortCount=" + mOutputPortCount +
- ",mProperties=" + mProperties);
+ ",mProperties=" + mProperties +
+ ",mIsPrivate=" + mIsPrivate);
}
public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
@@ -182,7 +208,8 @@
int inputPorts = in.readInt();
int outputPorts = in.readInt();
Bundle properties = in.readBundle();
- return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties);
+ boolean isPrivate = (in.readInt() == 1);
+ return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties, isPrivate);
}
public MidiDeviceInfo[] newArray(int size) {
@@ -200,5 +227,6 @@
parcel.writeInt(mInputPortCount);
parcel.writeInt(mOutputPortCount);
parcel.writeBundle(mProperties);
+ parcel.writeInt(mIsPrivate ? 1 : 0);
}
}
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 3317baa..24ef528 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -16,21 +16,22 @@
package android.media.midi;
+import android.os.IBinder;
+import android.os.Binder;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.system.OsConstants;
import android.util.Log;
+import libcore.io.IoUtils;
+
import java.io.Closeable;
import java.io.IOException;
-import java.util.ArrayList;
/**
- * This class is used to provide the implemention of MIDI device.
- * Applications may call {@link MidiManager#createDeviceServer}
- * to create an instance of this class to implement a virtual MIDI device.
+ * Internal class used for providing an implementation for a MIDI device.
*
- * CANDIDATE FOR PUBLIC API
* @hide
*/
public final class MidiDeviceServer implements Closeable {
@@ -40,64 +41,36 @@
// MidiDeviceInfo for the device implemented by this server
private MidiDeviceInfo mDeviceInfo;
- private int mInputPortCount;
- private int mOutputPortCount;
+ private final int mInputPortCount;
+ private final int mOutputPortCount;
- // output ports for receiving messages from our clients
- // we can have only one per port number
- private MidiOutputPort[] mInputPortSenders;
+ // MidiReceivers for receiving data on our input ports
+ private final MidiReceiver[] mInputPortReceivers;
- // receivers attached to our input ports
- private ArrayList<MidiReceiver>[] mInputPortReceivers;
+ // MidiDispatchers for sending data on our output ports
+ private MidiDispatcher[] mOutputPortDispatchers;
- // input ports for sending messages to our clients
- // we can have multiple outputs per port number
- private ArrayList<MidiInputPort>[] mOutputPortReceivers;
-
- // subclass of MidiInputPort for passing to clients
- // that notifies us when the connection has failed
- private class ServerInputPort extends MidiInputPort {
- ServerInputPort(ParcelFileDescriptor pfd, int portNumber) {
- super(pfd, portNumber);
- }
-
- @Override
- public void onIOException() {
- synchronized (mOutputPortReceivers) {
- mOutputPortReceivers[getPortNumber()].clear();
- }
- }
- }
-
- // subclass of MidiOutputPort for passing to clients
- // that notifies us when the connection has failed
- private class ServerOutputPort extends MidiOutputPort {
- ServerOutputPort(ParcelFileDescriptor pfd, int portNumber) {
- super(pfd, portNumber);
- }
-
- @Override
- public void onIOException() {
- synchronized (mInputPortSenders) {
- mInputPortSenders[getPortNumber()] = null;
- }
- }
- }
+ // MidiOutputPorts for clients connected to our input ports
+ private final MidiOutputPort[] mInputPortOutputPorts;
// Binder interface stub for receiving connection requests from clients
private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
@Override
public ParcelFileDescriptor openInputPort(int portNumber) {
+ if (mDeviceInfo.isPrivate()) {
+ if (Binder.getCallingUid() != Process.myUid()) {
+ throw new SecurityException("Can't access private device from different UID");
+ }
+ }
+
if (portNumber < 0 || portNumber >= mInputPortCount) {
Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
return null;
}
- ParcelFileDescriptor result = null;
-
- synchronized (mInputPortSenders) {
- if (mInputPortSenders[portNumber] != null) {
+ synchronized (mInputPortOutputPorts) {
+ if (mInputPortOutputPorts[portNumber] != null) {
Log.d(TAG, "port " + portNumber + " already open");
return null;
}
@@ -105,47 +78,86 @@
try {
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
- MidiOutputPort newOutputPort = new ServerOutputPort(pair[0], portNumber);
- mInputPortSenders[portNumber] = newOutputPort;
- result = pair[1];
+ final MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
+ mInputPortOutputPorts[portNumber] = outputPort;
+ final int portNumberF = portNumber;
+ final MidiReceiver inputPortReceviver = mInputPortReceivers[portNumber];
- ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber];
- synchronized (receivers) {
- for (int i = 0; i < receivers.size(); i++) {
- newOutputPort.connect(receivers.get(i));
+ outputPort.connect(new MidiReceiver() {
+ @Override
+ public void post(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+ try {
+ inputPortReceviver.post(msg, offset, count, timestamp);
+ } catch (IOException e) {
+ IoUtils.closeQuietly(mInputPortOutputPorts[portNumberF]);
+ mInputPortOutputPorts[portNumberF] = null;
+ // FIXME also flush the receiver
+ }
}
- }
+ });
+
+ return pair[1];
} catch (IOException e) {
Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
return null;
}
}
-
- return result;
}
@Override
public ParcelFileDescriptor openOutputPort(int portNumber) {
+ if (mDeviceInfo.isPrivate()) {
+ if (Binder.getCallingUid() != Process.myUid()) {
+ throw new SecurityException("Can't access private device from different UID");
+ }
+ }
+
if (portNumber < 0 || portNumber >= mOutputPortCount) {
Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
return null;
}
- synchronized (mOutputPortReceivers) {
- try {
- ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
- OsConstants.SOCK_SEQPACKET);
- mOutputPortReceivers[portNumber].add(new ServerInputPort(pair[0], portNumber));
- return pair[1];
- } catch (IOException e) {
- Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
- return null;
- }
+
+ try {
+ ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
+ OsConstants.SOCK_SEQPACKET);
+ final MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
+ final MidiSender sender = mOutputPortDispatchers[portNumber].getSender();
+ sender.connect(new MidiReceiver() {
+ @Override
+ public void post(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+ try {
+ inputPort.post(msg, offset, count, timestamp);
+ } catch (IOException e) {
+ IoUtils.closeQuietly(inputPort);
+ sender.disconnect(this);
+ // FIXME also flush the receiver?
+ }
+ }
+ });
+
+ return pair[1];
+ } catch (IOException e) {
+ Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
+ return null;
}
}
};
- /* package */ MidiDeviceServer(IMidiManager midiManager) {
+ /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
+ int numOutputPorts) {
mMidiManager = midiManager;
+ mInputPortReceivers = inputPortReceivers;
+ mInputPortCount = inputPortReceivers.length;
+ mOutputPortCount = numOutputPorts;
+
+ mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
+
+ mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
+ for (int i = 0; i < numOutputPorts; i++) {
+ mOutputPortDispatchers[i] = new MidiDispatcher();
+ }
}
/* package */ IMidiDeviceServer getBinderInterface() {
@@ -157,19 +169,6 @@
throw new IllegalStateException("setDeviceInfo should only be called once");
}
mDeviceInfo = deviceInfo;
- mInputPortCount = deviceInfo.getInputPortCount();
- mOutputPortCount = deviceInfo.getOutputPortCount();
- mInputPortSenders = new MidiOutputPort[mInputPortCount];
-
- mInputPortReceivers = new ArrayList[mInputPortCount];
- for (int i = 0; i < mInputPortCount; i++) {
- mInputPortReceivers[i] = new ArrayList<MidiReceiver>();
- }
-
- mOutputPortReceivers = new ArrayList[mOutputPortCount];
- for (int i = 0; i < mOutputPortCount; i++) {
- mOutputPortReceivers[i] = new ArrayList<MidiInputPort>();
- }
}
@Override
@@ -183,86 +182,13 @@
}
/**
- * Returns a {@link MidiDeviceInfo} object, which describes this device.
- *
- * @return the {@link MidiDeviceInfo} object
+ * Returns an array of {@link MidiReceiver} for the device's output ports.
+ * Clients can use these receivers to send data out the device's output ports.
+ * @return array of MidiReceivers
*/
- public MidiDeviceInfo getInfo() {
- return mDeviceInfo;
- }
-
- /**
- * Called to open a {@link MidiSender} to allow receiving MIDI messages
- * on the device's input port for the specified port number.
- *
- * @param portNumber the number of the input port
- * @return the {@link MidiSender}
- */
- public MidiSender openInputPortSender(int portNumber) {
- if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
- throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
- }
- final int portNumberF = portNumber;
- return new MidiSender() {
-
- @Override
- public void connect(MidiReceiver receiver) {
- // We always synchronize on mInputPortSenders before receivers if we need to
- // synchronize on both.
- synchronized (mInputPortSenders) {
- ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
- synchronized (receivers) {
- receivers.add(receiver);
- MidiOutputPort outputPort = mInputPortSenders[portNumberF];
- if (outputPort != null) {
- outputPort.connect(receiver);
- }
- }
- }
- }
-
- @Override
- public void disconnect(MidiReceiver receiver) {
- // We always synchronize on mInputPortSenders before receivers if we need to
- // synchronize on both.
- synchronized (mInputPortSenders) {
- ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
- synchronized (receivers) {
- receivers.remove(receiver);
- MidiOutputPort outputPort = mInputPortSenders[portNumberF];
- if (outputPort != null) {
- outputPort.disconnect(receiver);
- }
- }
- }
- }
- };
- }
-
- /**
- * Called to open a {@link MidiReceiver} to allow sending MIDI messages
- * on the virtual device's output port for the specified port number.
- *
- * @param portNumber the number of the output port
- * @return the {@link MidiReceiver}
- */
- public MidiReceiver openOutputPortReceiver(int portNumber) {
- if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
- throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
- }
- final int portNumberF = portNumber;
- return new MidiReceiver() {
-
- @Override
- public void post(byte[] msg, int offset, int count, long timestamp) throws IOException {
- ArrayList<MidiInputPort> receivers = mOutputPortReceivers[portNumberF];
- synchronized (receivers) {
- for (int i = 0; i < receivers.size(); i++) {
- // FIXME catch errors and remove dead ones
- receivers.get(i).post(msg, offset, count, timestamp);
- }
- }
- }
- };
+ public MidiReceiver[] getOutputPortReceivers() {
+ MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
+ System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
+ return receivers;
}
}
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
new file mode 100644
index 0000000..1d91be2
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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.midi;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * A service that implements a virtual MIDI device.
+ * Subclasses must implement the {@link #getInputPortReceivers} method to provide a
+ * list of {@link MidiReceiver}s to receive data sent to the device's input ports.
+ * Similarly, subclasses can call {@link #getOutputPortReceivers} to fetch a list
+ * of {@link MidiReceiver}s for sending data out the output ports.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * an intent filter with the {@link #SERVICE_INTERFACE} action
+ * and meta-data to describe the virtual device.
+ For example:</p>
+ * <pre>
+ * <service android:name=".VirtualDeviceService"
+ * android:label="@string/service_name">
+ * <intent-filter>
+ * <action android:name="android.media.midi.MidiDeviceService" />
+ * </intent-filter>
+ * <meta-data android:name="android.media.midi.MidiDeviceService"
+ android:resource="@xml/device_info" />
+ * </service></pre>
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+abstract public class MidiDeviceService extends Service {
+ private static final String TAG = "MidiDeviceService";
+
+ public static final String SERVICE_INTERFACE = "android.media.midi.MidiDeviceService";
+
+ private IMidiManager mMidiManager;
+ private MidiDeviceServer mServer;
+
+ @Override
+ public void onCreate() {
+ mMidiManager = IMidiManager.Stub.asInterface(
+ ServiceManager.getService(Context.MIDI_SERVICE));
+ MidiDeviceServer server;
+ try {
+ MidiDeviceInfo deviceInfo = mMidiManager.getServiceDeviceInfo(getPackageName(),
+ this.getClass().getName());
+ MidiReceiver[] inputPortReceivers = getInputPortReceivers();
+ if (inputPortReceivers == null) {
+ inputPortReceivers = new MidiReceiver[0];
+ }
+ server = new MidiDeviceServer(mMidiManager, inputPortReceivers,
+ deviceInfo.getOutputPortCount());
+ server.setDeviceInfo(deviceInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo");
+ server = null;
+ }
+ mServer = server;
+ }
+
+ /**
+ * Returns an array of {@link MidiReceiver} for the device's input ports.
+ * Subclasses must override this to provide the receivers which will receive
+ * data sent to the device's input ports. An empty array or null should be returned if
+ * the device has no input ports.
+ * @return array of MidiReceivers
+ */
+ abstract public MidiReceiver[] getInputPortReceivers();
+
+ /**
+ * Returns an array of {@link MidiReceiver} for the device's output ports.
+ * These can be used to send data out the device's output ports.
+ * @return array of MidiReceivers
+ */
+ public MidiReceiver[] getOutputPortReceivers() {
+ if (mServer == null) {
+ return null;
+ } else {
+ return mServer.getOutputPortReceivers();
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
+ return mServer.getBinderInterface().asBinder();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/media/java/android/media/midi/MidiDispatcher.java b/media/java/android/media/midi/MidiDispatcher.java
new file mode 100644
index 0000000..165061f
--- /dev/null
+++ b/media/java/android/media/midi/MidiDispatcher.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 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.midi;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Utility class for dispatching MIDI data to a list of {@link MidiReceiver}s.
+ * This class subclasses {@link MidiReceiver} and dispatches any data it receives
+ * to its receiver list. Any receivers that throw an exception upon receiving data will
+ * be automatically removed from the receiver list, but no IOException will be returned
+ * from the dispatcher's {@link #post} in that case.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public class MidiDispatcher implements MidiReceiver {
+
+ private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
+
+ private final MidiSender mSender = new MidiSender() {
+ /**
+ * Called to connect a {@link MidiReceiver} to the sender
+ *
+ * @param receiver the receiver to connect
+ */
+ public void connect(MidiReceiver receiver) {
+ mReceivers.add(receiver);
+ }
+
+ /**
+ * Called to disconnect a {@link MidiReceiver} from the sender
+ *
+ * @param receiver the receiver to disconnect
+ */
+ public void disconnect(MidiReceiver receiver) {
+ mReceivers.remove(receiver);
+ }
+ };
+
+ /**
+ * Returns whether this dispatcher contains any receivers.
+ * @return true if the receiver list is not empty
+ */
+ public boolean hasReceivers() {
+ return mReceivers.size() > 0;
+ }
+
+ /**
+ * Returns a {@link MidiSender} which is used to add and remove {@link MidiReceiver}s
+ * to the dispatcher's receiver list.
+ * @return the dispatcher's MidiSender
+ */
+ public MidiSender getSender() {
+ return mSender;
+ }
+
+ @Override
+ public void post(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ synchronized (mReceivers) {
+ for (int i = 0; i < mReceivers.size(); ) {
+ MidiReceiver receiver = mReceivers.get(i);
+ try {
+ receiver.post(msg, offset, count, timestamp);
+ i++; // increment only on success. on failure we remove the receiver
+ // so i should not be incremented
+ } catch (IOException e) {
+ // if the receiver fails we remove the receiver but do not propogate the exception
+ mSender.disconnect(receiver);
+ }
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index a502e3e..ca7d3c2 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -16,7 +16,11 @@
package android.media.midi;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.Bundle;
@@ -49,7 +53,7 @@
new HashMap<DeviceCallback,DeviceListener>();
// Binder stub for receiving device notifications from MidiService
- private class DeviceListener extends IMidiListener.Stub {
+ private class DeviceListener extends IMidiDeviceListener.Stub {
private final DeviceCallback mCallback;
private final Handler mHandler;
@@ -88,22 +92,33 @@
/**
* Callback class used for clients to receive MIDI device added and removed notifications
*/
- public static class DeviceCallback {
+ abstract public static class DeviceCallback {
/**
* Called to notify when a new MIDI device has been added
*
* @param device a {@link MidiDeviceInfo} for the newly added device
*/
- public void onDeviceAdded(MidiDeviceInfo device) {
- }
+ abstract public void onDeviceAdded(MidiDeviceInfo device);
/**
* Called to notify when a MIDI device has been removed
*
* @param device a {@link MidiDeviceInfo} for the removed device
*/
- public void onDeviceRemoved(MidiDeviceInfo device) {
- }
+ abstract public void onDeviceRemoved(MidiDeviceInfo device);
+ }
+
+ /**
+ * Callback class used for receiving the results of {@link #openDevice}
+ */
+ abstract public static class DeviceOpenCallback {
+ /**
+ * Called to respond to a {@link #openDevice} request
+ *
+ * @param deviceInfo the {@link MidiDeviceInfo} for the device to open
+ * @param device a {@link MidiDevice} for opened device, or null if opening failed
+ */
+ abstract public void onDeviceOpened(MidiDeviceInfo deviceInfo, MidiDevice device);
}
/**
@@ -163,33 +178,85 @@
}
}
+ private void sendOpenDeviceResponse(final MidiDeviceInfo deviceInfo, final MidiDevice device,
+ final DeviceOpenCallback callback, Handler handler) {
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override public void run() {
+ callback.onDeviceOpened(deviceInfo, device);
+ }
+ });
+ } else {
+ callback.onDeviceOpened(deviceInfo, device);
+ }
+ }
+
/**
* Opens a MIDI device for reading and writing.
*
* @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open
- * @return a {@link MidiDevice} object for the device
+ * @param callback a {@link #DeviceOpenCallback} to be called to receive the result
+ * @param handler the {@link android.os.Handler Handler} that will be used for delivering
+ * the result. If handler is null, then the thread used for the
+ * callback is unspecified.
*/
- public MidiDevice openDevice(MidiDeviceInfo deviceInfo) {
+ public void openDevice(MidiDeviceInfo deviceInfo, DeviceOpenCallback callback,
+ Handler handler) {
+ MidiDevice device = null;
try {
IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo);
if (server == null) {
- Log.e(TAG, "could not open device " + deviceInfo);
- return null;
+ ServiceInfo serviceInfo = (ServiceInfo)deviceInfo.getProperties().getParcelable(
+ MidiDeviceInfo.PROPERTY_SERVICE_INFO);
+ if (serviceInfo == null) {
+ Log.e(TAG, "no ServiceInfo for " + deviceInfo);
+ } else {
+ Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
+ intent.setComponent(new ComponentName(serviceInfo.packageName,
+ serviceInfo.name));
+ final MidiDeviceInfo deviceInfoF = deviceInfo;
+ final DeviceOpenCallback callbackF = callback;
+ final Handler handlerF = handler;
+ if (mContext.bindService(intent,
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ IMidiDeviceServer server =
+ IMidiDeviceServer.Stub.asInterface(binder);
+ MidiDevice device = new MidiDevice(deviceInfoF, server);
+ sendOpenDeviceResponse(deviceInfoF, device, callbackF, handlerF);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // FIXME - anything to do here?
+ }
+ },
+ Context.BIND_AUTO_CREATE))
+ {
+ // return immediately to avoid calling sendOpenDeviceResponse below
+ return;
+ } else {
+ Log.e(TAG, "Unable to bind service: " + intent);
+ }
+ }
+ } else {
+ device = new MidiDevice(deviceInfo, server);
}
- return new MidiDevice(deviceInfo, server);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openDevice");
}
- return null;
+ sendOpenDeviceResponse(deviceInfo, device, callback, handler);
}
/** @hide */
- public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
- Bundle properties, boolean isPrivate, int type) {
+ public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
+ int numOutputPorts, Bundle properties, int type) {
try {
- MidiDeviceServer server = new MidiDeviceServer(mService);
+ MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
+ numOutputPorts);
MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
- numInputPorts, numOutputPorts, properties, isPrivate, type);
+ inputPortReceivers.length, numOutputPorts, properties, type);
if (deviceInfo == null) {
Log.e(TAG, "registerVirtualDevice failed");
return null;
@@ -201,21 +268,4 @@
return null;
}
}
-
- /**
- * Creates a new MIDI virtual device.
- *
- * @param numInputPorts number of input ports for the virtual device
- * @param numOutputPorts number of output ports for the virtual device
- * @param properties a {@link android.os.Bundle} containing properties describing the device
- * @param isPrivate true if this device should only be visible and accessible to apps
- * with the same UID as the caller
- * @return a {@link MidiDeviceServer} object to locally represent the device
- */
- public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
- Bundle properties, boolean isPrivate) {
- return createDeviceServer(numInputPorts, numOutputPorts, properties,
- isPrivate, MidiDeviceInfo.TYPE_VIRTUAL);
- }
-
}
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 83ddeeb..c195603 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -23,7 +23,6 @@
import java.io.FileInputStream;
import java.io.IOException;
-import java.util.ArrayList;
/**
* This class is used for receiving data from a port on a MIDI device
@@ -35,11 +34,7 @@
private static final String TAG = "MidiOutputPort";
private final FileInputStream mInputStream;
-
- // array of receiver lists, indexed by port number
- private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
-
- private int mReceiverCount; // total number of receivers for all ports
+ private final MidiDispatcher mDispatcher = new MidiDispatcher();
// This thread reads MIDI events from a socket and distributes them to the list of
// MidiReceivers attached to this device.
@@ -47,7 +42,6 @@
@Override
public void run() {
byte[] buffer = new byte[MAX_PACKET_SIZE];
- ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
try {
while (true) {
@@ -55,77 +49,39 @@
int count = mInputStream.read(buffer);
if (count < 0) {
break;
+ // FIXME - inform receivers here?
}
int offset = getMessageOffset(buffer, count);
int size = getMessageSize(buffer, count);
long timestamp = getMessageTimeStamp(buffer, count);
- synchronized (mReceivers) {
- for (int i = 0; i < mReceivers.size(); i++) {
- MidiReceiver receiver = mReceivers.get(i);
- try {
- receiver.post(buffer, offset, size, timestamp);
- } catch (IOException e) {
- Log.e(TAG, "post failed");
- deadReceivers.add(receiver);
- }
- }
- // remove any receivers that failed
- if (deadReceivers.size() > 0) {
- for (MidiReceiver receiver: deadReceivers) {
- mReceivers.remove(receiver);
- mReceiverCount--;
- }
- deadReceivers.clear();
- }
- // exit if we have no receivers left
- if (mReceiverCount == 0) {
- break;
- }
- }
+ // dispatch to all our receivers
+ mDispatcher.post(buffer, offset, size, timestamp);
}
} catch (IOException e) {
- // report I/O failure
+ // FIXME report I/O failure?
Log.e(TAG, "read failed");
} finally {
IoUtils.closeQuietly(mInputStream);
- onIOException();
}
}
};
- /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
+ /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
super(portNumber);
mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ mThread.start();
}
- /**
- * Connects a {@link MidiReceiver} to the output port to allow receiving
- * MIDI messages from the port.
- *
- * @param receiver the receiver to connect
- */
+ @Override
public void connect(MidiReceiver receiver) {
- synchronized (mReceivers) {
- mReceivers.add(receiver);
- if (mReceiverCount++ == 0) {
- mThread.start();
- }
- }
+ mDispatcher.getSender().connect(receiver);
}
- /**
- * Disconnects a {@link MidiReceiver} from the output port.
- *
- * @param receiver the receiver to connect
- */
+ @Override
public void disconnect(MidiReceiver receiver) {
- synchronized (mReceivers) {
- if (mReceivers.remove(receiver)) {
- mReceiverCount--;
- }
- }
+ mDispatcher.getSender().disconnect(receiver);
}
@Override
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index a73209b..dfe2844 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -256,7 +256,7 @@
dump();
// allocate a channel
- channel = allocateChannel_l(priority);
+ channel = allocateChannel_l(priority, sampleID);
// no channel allocated - return 0
if (!channel) {
@@ -271,13 +271,25 @@
return channelID;
}
-SoundChannel* SoundPool::allocateChannel_l(int priority)
+SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
{
List<SoundChannel*>::iterator iter;
SoundChannel* channel = NULL;
- // allocate a channel
+ // check if channel for given sampleID still available
if (!mChannels.empty()) {
+ for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
+ if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
+ channel = *iter;
+ mChannels.erase(iter);
+ ALOGV("Allocated recycled channel for same sampleID");
+ break;
+ }
+ }
+ }
+
+ // allocate any channel
+ if (!channel && !mChannels.empty()) {
iter = mChannels.begin();
if (priority >= (*iter)->priority()) {
channel = *iter;
@@ -648,6 +660,7 @@
void SoundChannel::init(SoundPool* soundPool)
{
mSoundPool = soundPool;
+ mPrevSampleID = -1;
}
// call with sound pool lock held
@@ -656,7 +669,7 @@
{
sp<AudioTrack> oldTrack;
sp<AudioTrack> newTrack;
- status_t status;
+ status_t status = NO_ERROR;
{ // scope for the lock
Mutex::Autolock lock(&mLock);
@@ -701,38 +714,43 @@
}
#endif
- // mToggle toggles each time a track is started on a given channel.
- // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
- // as callback user data. This enables the detection of callbacks received from the old
- // audio track while the new one is being started and avoids processing them with
- // wrong audio audio buffer size (mAudioBufferSize)
- unsigned long toggle = mToggle ^ 1;
- void *userData = (void *)((unsigned long)this | toggle);
- audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);
+ if (!mAudioTrack.get() || mPrevSampleID != sample->sampleID()) {
+ // mToggle toggles each time a track is started on a given channel.
+ // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
+ // as callback user data. This enables the detection of callbacks received from the old
+ // audio track while the new one is being started and avoids processing them with
+ // wrong audio audio buffer size (mAudioBufferSize)
+ unsigned long toggle = mToggle ^ 1;
+ void *userData = (void *)((unsigned long)this | toggle);
+ audio_channel_mask_t channelMask = audio_channel_out_mask_from_count(numChannels);
- // do not create a new audio track if current track is compatible with sample parameters
-#ifdef USE_SHARED_MEM_BUFFER
- newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
- channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData);
-#else
- uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
- newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
- channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
- bufferFrames);
-#endif
- oldTrack = mAudioTrack;
- status = newTrack->initCheck();
- if (status != NO_ERROR) {
- ALOGE("Error creating AudioTrack");
- goto exit;
+ // do not create a new audio track if current track is compatible with sample parameters
+ #ifdef USE_SHARED_MEM_BUFFER
+ newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
+ channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData);
+ #else
+ uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
+ newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
+ channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
+ bufferFrames);
+ #endif
+ oldTrack = mAudioTrack;
+ status = newTrack->initCheck();
+ if (status != NO_ERROR) {
+ ALOGE("Error creating AudioTrack");
+ goto exit;
+ }
+ // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
+ mToggle = toggle;
+ mAudioTrack = newTrack;
+ ALOGV("using new track %p for sample %d", newTrack.get(), sample->sampleID());
+ } else {
+ newTrack = mAudioTrack;
+ newTrack->setSampleRate(sampleRate);
+ ALOGV("reusing track %p for sample %d", mAudioTrack.get(), sample->sampleID());
}
- ALOGV("setVolume %p", newTrack.get());
newTrack->setVolume(leftVolume, rightVolume);
newTrack->setLoop(0, frameCount, loop);
-
- // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
- mToggle = toggle;
- mAudioTrack = newTrack;
mPos = 0;
mSample = sample;
mChannelID = nextChannelID;
@@ -875,6 +893,7 @@
setVolume_l(0, 0);
ALOGV("stop");
mAudioTrack->stop();
+ mPrevSampleID = mSample->sampleID();
mSample.clear();
mState = IDLE;
mPriority = IDLE_PRIORITY;
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 9d9cbdf..f520406 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -136,6 +136,7 @@
void nextEvent();
int nextChannelID() { return mNextEvent.channelID(); }
void dump();
+ int getPrevSampleID(void) { return mPrevSampleID; }
private:
static void callback(int event, void* user, void *info);
@@ -152,6 +153,7 @@
int mAudioBufferSize;
unsigned long mToggle;
bool mAutoPaused;
+ int mPrevSampleID;
};
// application object for managing a pool of sounds
@@ -193,7 +195,7 @@
sp<Sample> findSample(int sampleID) { return mSamples.valueFor(sampleID); }
SoundChannel* findChannel (int channelID);
SoundChannel* findNextChannel (int channelID);
- SoundChannel* allocateChannel_l(int priority);
+ SoundChannel* allocateChannel_l(int priority, int sampleID);
void moveToFront_l(SoundChannel* channel);
void notify(SoundPoolEvent event);
void dump();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0168b95..275a6be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -172,7 +172,7 @@
private static final String KEYGUARD_ANALYTICS_SETTING = "keyguard_analytics";
/** The stream type that the lock sounds are tied to. */
- private int mMasterStreamType;
+ private int mUiSoundsStreamType;
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
@@ -1244,10 +1244,10 @@
if (mAudioManager == null) {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
if (mAudioManager == null) return;
- mMasterStreamType = mAudioManager.getMasterStreamType();
+ mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType();
}
// If the stream is muted, don't play the sound
- if (mAudioManager.isStreamMute(mMasterStreamType)) return;
+ if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
mLockSoundStreamId = mLockSounds.play(soundId,
mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 31264ee..51adaac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -125,8 +125,6 @@
private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15;
private static final int MSG_INTERNAL_RINGER_MODE_CHANGED = 16;
- // Pseudo stream type for master volume
- private static final int STREAM_MASTER = -100;
// Pseudo stream type for remote volume
private static final int STREAM_REMOTE_MUSIC = -200;
@@ -154,10 +152,6 @@
private int mLastRingerProgress = 0;
private int mDemoIcon;
- // True if we want to play tones on the system stream when the master stream is specified.
- private final boolean mPlayMasterStreamTones;
-
-
/** Volume panel content view */
private final View mView;
/** Dialog hosting the panel */
@@ -213,12 +207,6 @@
com.android.systemui.R.drawable.ic_ringer_audible,
com.android.systemui.R.drawable.ic_ringer_mute,
true),
- // for now, use media resources for master volume
- MasterStream(STREAM_MASTER,
- R.string.volume_icon_description_media, //FIXME should have its own description
- IC_AUDIO_VOL,
- IC_AUDIO_VOL_MUTE,
- false),
RemoteStream(STREAM_REMOTE_MUSIC,
R.string.volume_icon_description_media, //FIXME should have its own description
com.android.systemui.R.drawable.ic_audio_remote,
@@ -249,7 +237,6 @@
StreamResources.MediaStream,
StreamResources.NotificationStream,
StreamResources.AlarmStream,
- StreamResources.MasterStream,
StreamResources.RemoteStream
};
@@ -371,15 +358,6 @@
mSecondaryIconTransition = new SecondaryIconTransition();
mIconPulser = new IconPulser(context);
- // For now, only show master volume if master volume is supported
- final Resources res = context.getResources();
- final boolean useMasterVolume = res.getBoolean(R.bool.config_useMasterVolume);
- if (useMasterVolume) {
- for (int i = 0; i < STREAMS.length; i++) {
- StreamResources streamRes = STREAMS[i];
- streamRes.show = (streamRes.streamType == STREAM_MASTER);
- }
- }
if (LOGD) Log.d(mTag, "new VolumePanel");
mDisabledAlpha = 0.5f;
@@ -419,6 +397,7 @@
mDialog.create();
+ final Resources res = context.getResources();
window.setAttributes(getDialogLayoutParams(window, res));
updateWidth();
@@ -447,16 +426,12 @@
mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
- if (mZenController != null && !useMasterVolume) {
+ if (mZenController != null) {
mZenModeAvailable = mZenController.isZenAvailable();
mNotificationEffectsSuppressor = mZenController.getEffectsSuppressor();
mZenController.addCallback(mZenCallback);
}
- final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume);
- final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds);
- mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
-
registerReceiver();
}
@@ -489,7 +464,6 @@
pw.print(" mDisabledAlpha="); pw.println(mDisabledAlpha);
pw.print(" mLastRingerMode="); pw.println(mLastRingerMode);
pw.print(" mLastRingerProgress="); pw.println(mLastRingerProgress);
- pw.print(" mPlayMasterStreamTones="); pw.println(mPlayMasterStreamTones);
pw.print(" isShowing()="); pw.println(isShowing());
pw.print(" mCallback="); pw.println(mCallback);
pw.print(" sConfirmSafeVolumeDialog=");
@@ -576,9 +550,7 @@
}
private boolean isMuted(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.isMasterMute();
- } else if (streamType == STREAM_REMOTE_MUSIC) {
+ if (streamType == STREAM_REMOTE_MUSIC) {
// TODO do we need to support a distinct mute property for remote?
return false;
} else {
@@ -587,9 +559,7 @@
}
private int getStreamMaxVolume(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.getMasterMaxVolume();
- } else if (streamType == STREAM_REMOTE_MUSIC) {
+ if (streamType == STREAM_REMOTE_MUSIC) {
if (mStreamControls != null) {
StreamControl sc = mStreamControls.get(streamType);
if (sc != null && sc.controller != null) {
@@ -604,9 +574,7 @@
}
private int getStreamVolume(int streamType) {
- if (streamType == STREAM_MASTER) {
- return mAudioManager.getLastAudibleMasterVolume();
- } else if (streamType == STREAM_REMOTE_MUSIC) {
+ if (streamType == STREAM_REMOTE_MUSIC) {
if (mStreamControls != null) {
StreamControl sc = mStreamControls.get(streamType);
if (sc != null && sc.controller != null) {
@@ -628,11 +596,7 @@
Log.w(mTag, "Adjusting remote volume without a controller!");
}
} else if (getStreamVolume(sc.streamType) != index) {
- if (sc.streamType == STREAM_MASTER) {
- mAudioManager.setMasterVolume(index, flags);
- } else {
- mAudioManager.setStreamVolume(sc.streamType, index, flags);
- }
+ mAudioManager.setStreamVolume(sc.streamType, index, flags);
}
}
@@ -833,7 +797,7 @@
sc.icon.setAlpha(mDisabledAlpha);
sc.icon.setClickable(false);
} else if (fixedVolume ||
- (sc.streamType != mAudioManager.getMasterStreamType() && !isRinger && muted) ||
+ (sc.streamType != mAudioManager.getUiSoundsStreamType() && !isRinger && muted) ||
(sSafetyWarning != null)) {
sc.seekbarView.setEnabled(false);
} else {
@@ -977,10 +941,6 @@
obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
}
- public void postMasterVolumeChanged(int flags) {
- postVolumeChanged(STREAM_MASTER, flags);
- }
-
public void postMuteChanged(int streamType, int flags) {
if (hasMessages(MSG_VOLUME_CHANGED)) return;
synchronized (this) {
@@ -992,10 +952,6 @@
obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
}
- public void postMasterMuteChanged(int flags) {
- postMuteChanged(STREAM_MASTER, flags);
- }
-
public void postDisplaySafeVolumeWarning(int flags) {
if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
@@ -1192,9 +1148,7 @@
if (!isShowing()) {
int stream = (streamType == STREAM_REMOTE_MUSIC) ? -1 : streamType;
// when the stream is for remote playback, use -1 to reset the stream type evaluation
- if (stream != STREAM_MASTER) {
- mAudioManager.forceVolumeControlStream(stream);
- }
+ mAudioManager.forceVolumeControlStream(stream);
mDialog.show();
if (mCallback != null) {
mCallback.onVisible(true);
@@ -1360,16 +1314,6 @@
* Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
*/
private ToneGenerator getOrCreateToneGenerator(int streamType) {
- if (streamType == STREAM_MASTER) {
- // For devices that use the master volume setting only but still want to
- // play a volume-changed tone, direct the master volume pseudostream to
- // the system stream's tone generator.
- if (mPlayMasterStreamTones) {
- streamType = AudioManager.STREAM_SYSTEM;
- } else {
- return null;
- }
- }
synchronized (this) {
if (mToneGenerators[streamType] == null) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index f7f3bd8..7603c7d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -201,13 +201,8 @@
}
@Override
- public void masterVolumeChanged(int flags) throws RemoteException {
- mPanel.postMasterVolumeChanged(flags);
- }
-
- @Override
public void masterMuteChanged(int flags) throws RemoteException {
- mPanel.postMasterMuteChanged(flags);
+ // no-op
}
@Override
diff --git a/preloaded-classes b/preloaded-classes
index dee84f0..c8d8c5d 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1172,6 +1172,7 @@
android.telecom.InCallService
android.telephony.PhoneNumberUtils
android.telephony.Rlog
+android.telephony.SignalStrength
android.telephony.SubscriptionManager
android.telephony.TelephonyManager
android.text.AndroidBidi
diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java
index 59b82da..7f98b30 100644
--- a/services/core/java/com/android/server/MidiService.java
+++ b/services/core/java/com/android/server/MidiService.java
@@ -17,10 +17,18 @@
package com.android.server;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.XmlResourceParser;
+import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceServer;
-import android.media.midi.IMidiListener;
import android.media.midi.IMidiManager;
import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceService;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -28,12 +36,17 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
public class MidiService extends IMidiManager.Stub {
private static final String TAG = "MidiService";
@@ -53,6 +66,27 @@
// used for assigning IDs to MIDI devices
private int mNextDeviceId = 1;
+ private final PackageManager mPackageManager;
+
+ // PackageMonitor for listening to package changes
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ addPackageDeviceServers(packageName);
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ removePackageDeviceServers(packageName);
+ addPackageDeviceServers(packageName);
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ removePackageDeviceServers(packageName);
+ }
+ };
+
private final class Client implements IBinder.DeathRecipient {
// Binder token for this client
private final IBinder mToken;
@@ -61,7 +95,8 @@
// This client's PID
private final int mPid;
// List of all receivers for this client
- private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
+ private final ArrayList<IMidiDeviceListener> mListeners
+ = new ArrayList<IMidiDeviceListener>();
public Client(IBinder token) {
mToken = token;
@@ -73,11 +108,11 @@
return mUid;
}
- public void addListener(IMidiListener listener) {
+ public void addListener(IMidiDeviceListener listener) {
mListeners.add(listener);
}
- public void removeListener(IMidiListener listener) {
+ public void removeListener(IMidiDeviceListener listener) {
mListeners.remove(listener);
if (mListeners.size() == 0) {
removeClient(mToken);
@@ -90,7 +125,7 @@
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
- for (IMidiListener listener : mListeners) {
+ for (IMidiDeviceListener listener : mListeners) {
listener.onDeviceAdded(deviceInfo);
}
} catch (RemoteException e) {
@@ -104,7 +139,7 @@
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
- for (IMidiListener listener : mListeners) {
+ for (IMidiDeviceListener listener : mListeners) {
listener.onDeviceRemoved(deviceInfo);
}
} catch (RemoteException e) {
@@ -152,16 +187,17 @@
private final class Device implements IBinder.DeathRecipient {
private final IMidiDeviceServer mServer;
private final MidiDeviceInfo mDeviceInfo;
- // UID of device creator
+ // ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only)
+ private final ServiceInfo mServiceInfo;
+ // UID of device implementation
private final int mUid;
- // PID of device creator
- private final int mPid;
- public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo) {
+ public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
+ ServiceInfo serviceInfo, int uid) {
mServer = server;
mDeviceInfo = deviceInfo;
- mUid = Binder.getCallingUid();
- mPid = Binder.getCallingPid();
+ mServiceInfo = serviceInfo;
+ mUid = uid;
}
public MidiDeviceInfo getDeviceInfo() {
@@ -172,13 +208,20 @@
return mServer;
}
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ public String getPackageName() {
+ return (mServiceInfo == null ? null : mServiceInfo.packageName);
+ }
+
public boolean isUidAllowed(int uid) {
- // FIXME
- return true;
+ return (!mDeviceInfo.isPrivate() || mUid == uid);
}
public void binderDied() {
- synchronized (mDevicesByServer) {
+ synchronized (mDevicesByInfo) {
removeDeviceLocked(this);
}
}
@@ -189,32 +232,59 @@
sb.append(mDeviceInfo);
sb.append(" UID: ");
sb.append(mUid);
- sb.append(" PID: ");
- sb.append(mPid);
return sb.toString();
}
}
public MidiService(Context context) {
mContext = context;
- }
+ mPackageManager = context.getPackageManager();
+ mPackageMonitor.register(context, null, true);
- public void registerListener(IBinder token, IMidiListener listener) {
+ Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
+ List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent,
+ PackageManager.GET_META_DATA);
+ if (resolveInfos != null) {
+ int count = resolveInfos.size();
+ for (int i = 0; i < count; i++) {
+ ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
+ if (serviceInfo != null) {
+ addPackageDeviceServer(serviceInfo);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void registerListener(IBinder token, IMidiDeviceListener listener) {
Client client = getClient(token);
if (client == null) return;
client.addListener(listener);
}
- public void unregisterListener(IBinder token, IMidiListener listener) {
+ @Override
+ public void unregisterListener(IBinder token, IMidiDeviceListener listener) {
Client client = getClient(token);
if (client == null) return;
client.removeListener(listener);
}
public MidiDeviceInfo[] getDeviceList() {
- return mDevicesByInfo.keySet().toArray(new MidiDeviceInfo[0]);
+ ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
+ int uid = Binder.getCallingUid();
+
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ if (device.isUidAllowed(uid)) {
+ deviceInfos.add(device.getDeviceInfo());
+ }
+ }
+ }
+
+ return deviceInfos.toArray(new MidiDeviceInfo[0]);
}
+ @Override
public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
Device device = mDevicesByInfo.get(deviceInfo);
if (device == null) {
@@ -229,28 +299,64 @@
return device.getDeviceServer();
}
+ @Override
public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
- int numOutputPorts, Bundle properties, boolean isPrivate, int type) {
+ int numOutputPorts, Bundle properties, int type) {
if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("only system can create non-virtual devices");
}
- MidiDeviceInfo deviceInfo;
- Device device;
+ synchronized (mDevicesByInfo) {
+ return addDeviceLocked(type, numInputPorts, numOutputPorts, properties,
+ server, null, false, -1);
+ }
+ }
- synchronized (mDevicesByServer) {
- int id = mNextDeviceId++;
- deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, properties);
+ @Override
+ public void unregisterDeviceServer(IMidiDeviceServer server) {
+ synchronized (mDevicesByInfo) {
+ Device device = mDevicesByServer.get(server.asBinder());
+ if (device != null) {
+ removeDeviceLocked(device);
+ }
+ }
+ }
+
+ @Override
+ public MidiDeviceInfo getServiceDeviceInfo(String packageName, String className) {
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ ServiceInfo serviceInfo = device.getServiceInfo();
+ if (serviceInfo != null &&
+ packageName.equals(serviceInfo.packageName) &&
+ className.equals(serviceInfo.name)) {
+ return device.getDeviceInfo();
+ }
+ }
+ return null;
+ }
+ }
+
+ // synchronize on mDevicesByInfo
+ private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
+ Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo,
+ boolean isPrivate, int uid) {
+
+ int id = mNextDeviceId++;
+ MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
+ properties, isPrivate);
+ Device device = new Device(server, deviceInfo, serviceInfo, uid);
+
+ if (server != null) {
IBinder binder = server.asBinder();
- device = new Device(server, deviceInfo);
try {
binder.linkToDeath(device, 0);
} catch (RemoteException e) {
return null;
}
- mDevicesByInfo.put(deviceInfo, device);
- mDevicesByServer.put(server.asBinder(), device);
+ mDevicesByServer.put(binder, device);
}
+ mDevicesByInfo.put(deviceInfo, device);
synchronized (mClients) {
for (Client c : mClients.values()) {
@@ -261,16 +367,13 @@
return deviceInfo;
}
- public void unregisterDeviceServer(IMidiDeviceServer server) {
- synchronized (mDevicesByServer) {
- removeDeviceLocked(mDevicesByServer.get(server.asBinder()));
- }
- }
-
- // synchronize on mDevicesByServer
+ // synchronize on mDevicesByInfo
private void removeDeviceLocked(Device device) {
- if (mDevicesByServer.remove(device.getDeviceServer().asBinder()) != null) {
- mDevicesByInfo.remove(device.getDeviceInfo());
+ if (mDevicesByInfo.remove(device.getDeviceInfo()) != null) {
+ IMidiDeviceServer server = device.getDeviceServer();
+ if (server != null) {
+ mDevicesByServer.remove(server);
+ }
synchronized (mClients) {
for (Client c : mClients.values()) {
@@ -280,6 +383,134 @@
}
}
+ private void addPackageDeviceServers(String packageName) {
+ PackageInfo info;
+
+ try {
+ info = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
+ return;
+ }
+
+ ServiceInfo[] services = info.services;
+ if (services == null) return;
+ for (int i = 0; i < services.length; i++) {
+ addPackageDeviceServer(services[i]);
+ }
+ }
+
+ private void addPackageDeviceServer(ServiceInfo serviceInfo) {
+ XmlResourceParser parser = null;
+
+ try {
+ parser = serviceInfo.loadXmlMetaData(mPackageManager,
+ MidiDeviceService.SERVICE_INTERFACE);
+ if (parser == null) return;
+
+ Bundle properties = null;
+ int numInputPorts = 0;
+ int numOutputPorts = 0;
+ boolean isPrivate = false;
+
+ while (true) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ break;
+ } else if (eventType == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ if ("device".equals(tagName)) {
+ if (properties != null) {
+ Log.w(TAG, "nested <device> elements in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ properties = new Bundle();
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_SERVICE_INFO, serviceInfo);
+ numInputPorts = 0;
+ numOutputPorts = 0;
+ isPrivate = false;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ if ("private".equals(name)) {
+ isPrivate = "true".equals(value);
+ } else {
+ properties.putString(name, value);
+ }
+ }
+ } else if ("input-port".equals(tagName)) {
+ if (properties == null) {
+ Log.w(TAG, "<input-port> outside of <device> in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ numInputPorts++;
+ // TODO - add support for port properties
+ } else if ("output-port".equals(tagName)) {
+ if (properties == null) {
+ Log.w(TAG, "<output-port> outside of <device> in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ numOutputPorts++;
+ // TODO - add support for port properties
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ String tagName = parser.getName();
+ if ("device".equals(tagName)) {
+ if (properties != null) {
+ if (numInputPorts == 0 && numOutputPorts == 0) {
+ Log.w(TAG, "<device> with no ports in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+
+ int uid = -1;
+ if (isPrivate) {
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+ serviceInfo.packageName, 0);
+ uid = appInfo.uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "could not fetch ApplicationInfo for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ }
+
+ synchronized (mDevicesByInfo) {
+ addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL,
+ numInputPorts, numOutputPorts, properties,
+ null, serviceInfo, isPrivate, uid);
+ }
+ // setting properties to null signals that we are no longer
+ // processing a <device>
+ properties = null;
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + serviceInfo.toString(), e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ private void removePackageDeviceServers(String packageName) {
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ if (packageName.equals(device.getPackageName())) {
+ removeDeviceLocked(device);
+ }
+ }
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 008d718..61ff514 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8347,6 +8347,9 @@
synchronized (this) {
pkg = task.intent.getComponent().getPackageName();
}
+ // isSystemInitiated is used to distinguish between locked and pinned mode, as pinned mode
+ // is initiated by system after the pinning request was shown and locked mode is initiated
+ // by an authorized app directly
boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID;
if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) {
StatusBarManagerInternal statusBarManager = LocalServices.getService(
@@ -8367,7 +8370,9 @@
|| (task != mStackSupervisor.getFocusedStack().topTask()))) {
throw new IllegalArgumentException("Invalid task, not in foreground");
}
- mStackSupervisor.setLockTaskModeLocked(task, !isSystemInitiated,
+ mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated ?
+ ActivityManager.LOCK_TASK_MODE_PINNED :
+ ActivityManager.LOCK_TASK_MODE_LOCKED,
"startLockTask");
}
}
@@ -8453,7 +8458,8 @@
Log.d(TAG, "stopLockTaskMode");
// Stop lock task
synchronized (this) {
- mStackSupervisor.setLockTaskModeLocked(null, false, "stopLockTask");
+ mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE,
+ "stopLockTask");
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -8474,8 +8480,13 @@
@Override
public boolean isInLockTaskMode() {
+ return getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE;
+ }
+
+ @Override
+ public int getLockTaskModeState() {
synchronized (this) {
- return mStackSupervisor.isInLockTaskMode();
+ return mStackSupervisor.getLockTaskModeState();
}
}
@@ -18534,7 +18545,8 @@
return true;
}
- mStackSupervisor.setLockTaskModeLocked(null, false, "startUser");
+ mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE,
+ "startUser");
final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
if (userInfo == null) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index a260330..b3d3d6a 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -267,9 +267,11 @@
/** If non-null then the task specified remains in front and no other tasks may be started
* until the task exits or #stopLockTaskMode() is called. */
TaskRecord mLockTaskModeTask;
- /** Whether lock task has been entered by an authorized app and cannot
- * be exited. */
- private boolean mLockTaskIsLocked;
+ /** Store the current lock task mode. Possible values:
+ * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActicityManager#LOCK_TASK_MODE_LOCKED},
+ * {@link ActicityManager#LOCK_TASK_MODE_PINNED}
+ */
+ private int mLockTaskModeState;
/**
* Notifies the user when entering/exiting lock-task.
*/
@@ -3546,10 +3548,10 @@
}
void showLockTaskToast() {
- mLockTaskNotify.showToast(mLockTaskIsLocked);
+ mLockTaskNotify.showToast(mLockTaskModeState);
}
- void setLockTaskModeLocked(TaskRecord task, boolean isLocked, String reason) {
+ void setLockTaskModeLocked(TaskRecord task, int lockTaskModeState, String reason) {
if (task == null) {
// Take out of lock task mode if necessary
if (mLockTaskModeTask != null) {
@@ -3573,7 +3575,7 @@
lockTaskMsg.obj = mLockTaskModeTask.intent.getComponent().getPackageName();
lockTaskMsg.arg1 = mLockTaskModeTask.userId;
lockTaskMsg.what = LOCK_TASK_START_MSG;
- lockTaskMsg.arg2 = !isLocked ? 1 : 0;
+ lockTaskMsg.arg2 = lockTaskModeState;
mHandler.sendMessage(lockTaskMsg);
}
@@ -3591,8 +3593,8 @@
}
}
- boolean isInLockTaskMode() {
- return mLockTaskModeTask != null;
+ int getLockTaskModeState() {
+ return mLockTaskModeState;
}
private final class ActivityStackSupervisorHandler extends Handler {
@@ -3684,13 +3686,18 @@
mLockTaskNotify = new LockTaskNotify(mService.mContext);
}
mLockTaskNotify.show(true);
- mLockTaskIsLocked = msg.arg2 == 0;
+ mLockTaskModeState = msg.arg2;
if (getStatusBarService() != null) {
- int flags =
- StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK;
- if (!mLockTaskIsLocked) {
- flags ^= StatusBarManager.DISABLE_HOME
- | StatusBarManager.DISABLE_RECENT;
+ int flags = 0;
+ if (mLockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) {
+ flags = StatusBarManager.DISABLE_MASK
+ & (~StatusBarManager.DISABLE_BACK);
+ } else if (mLockTaskModeState ==
+ ActivityManager.LOCK_TASK_MODE_PINNED) {
+ flags = StatusBarManager.DISABLE_MASK
+ & (~StatusBarManager.DISABLE_BACK)
+ & (~StatusBarManager.DISABLE_HOME)
+ & (~StatusBarManager.DISABLE_RECENT);
}
getStatusBarService().disable(flags, mToken,
mService.mContext.getPackageName());
@@ -3724,7 +3731,8 @@
boolean shouldLockKeyguard = Settings.Secure.getInt(
mService.mContext.getContentResolver(),
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED) != 0;
- if (!mLockTaskIsLocked && shouldLockKeyguard) {
+ if (mLockTaskModeState == ActivityManager.LOCK_TASK_MODE_PINNED &&
+ shouldLockKeyguard) {
mWindowManager.lockNow(null);
mWindowManager.dismissKeyguard();
new LockPatternUtils(mService.mContext)
@@ -3735,6 +3743,8 @@
}
} catch (RemoteException ex) {
throw new RuntimeException(ex);
+ } finally {
+ mLockTaskModeState = ActivityManager.LOCK_TASK_MODE_NONE;
}
} break;
case CONTAINER_CALLBACK_TASK_LIST_EMPTY: {
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
index b3777ed..afde322 100644
--- a/services/core/java/com/android/server/am/LockTaskNotify.java
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.app.ActivityManager;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
@@ -44,15 +45,20 @@
mHandler = new H();
}
- public void showToast(boolean isLocked) {
- mHandler.obtainMessage(H.SHOW_TOAST, isLocked ? 1 : 0, 0 /* Not used */).sendToTarget();
+ public void showToast(int lockTaskModeState) {
+ mHandler.obtainMessage(H.SHOW_TOAST, lockTaskModeState, 0 /* Not used */).sendToTarget();
}
- public void handleShowToast(boolean isLocked) {
- String text = mContext.getString(isLocked
- ? R.string.lock_to_app_toast_locked : R.string.lock_to_app_toast);
- if (!isLocked && mAccessibilityManager.isEnabled()) {
- text = mContext.getString(R.string.lock_to_app_toast_accessible);
+ public void handleShowToast(int lockTaskModeState) {
+ String text = null;
+ if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) {
+ text = mContext.getString(R.string.lock_to_app_toast_locked);
+ } else if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_PINNED) {
+ text = mContext.getString(mAccessibilityManager.isEnabled()
+ ? R.string.lock_to_app_toast_accessible : R.string.lock_to_app_toast);
+ }
+ if (text == null) {
+ return;
}
if (mLastToast != null) {
mLastToast.cancel();
@@ -83,7 +89,7 @@
public void handleMessage(Message msg) {
switch(msg.what) {
case SHOW_TOAST:
- handleShowToast(msg.arg1 != 0);
+ handleShowToast(msg.arg1);
break;
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 018d77a..4b7f698 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -188,7 +188,6 @@
// AudioHandler messages
private static final int MSG_SET_DEVICE_VOLUME = 0;
private static final int MSG_PERSIST_VOLUME = 1;
- private static final int MSG_PERSIST_MASTER_VOLUME = 2;
private static final int MSG_PERSIST_RINGER_MODE = 3;
private static final int MSG_MEDIA_SERVER_DIED = 4;
private static final int MSG_PLAY_SOUND_EFFECT = 5;
@@ -239,10 +238,6 @@
private final Object mSoundEffectsLock = new Object();
private static final int NUM_SOUNDPOOL_CHANNELS = 4;
- // Internally master volume is a float in the 0.0 - 1.0 range,
- // but to support integer based AudioManager API we translate it to 0 - 100
- private static final int MAX_MASTER_VOLUME = 100;
-
// Maximum volume adjust steps allowed in a single batch call.
private static final int MAX_BATCH_VOLUME_ADJUST_STEPS = 4;
@@ -386,11 +381,6 @@
// Forced device usage for communications
private int mForcedUseForComm;
- // True if we have master volume support
- private final boolean mUseMasterVolume;
-
- private final int[] mMasterVolumeRamp;
-
// List of binder death handlers for setMode() client processes.
// The last process to have called setMode() is at the top of the list.
private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
@@ -595,10 +585,6 @@
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
- mUseMasterVolume = context.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
- mMasterVolumeRamp = context.getResources().getIntArray(
- com.android.internal.R.array.config_masterVolumeRamp);
// must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
// array initialized by updateStreamVolumeAlias()
@@ -647,8 +633,6 @@
context.registerReceiver(mReceiver, intentFilter);
- restoreMasterVolume();
-
LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal());
}
@@ -882,7 +866,7 @@
UserHandle.USER_CURRENT);
boolean masterMute = System.getIntForUser(cr, System.VOLUME_MASTER_MUTE,
- 0, UserHandle.USER_CURRENT) == 1;
+ 0, UserHandle.USER_CURRENT) == 1;
if (mUseFixedVolume) {
masterMute = false;
AudioSystem.setMasterVolume(1.0f);
@@ -1050,7 +1034,7 @@
// If either the client forces allowing ringer modes for this adjustment,
// or the stream type is one that is affected by ringer modes
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
- (streamTypeAlias == getMasterStreamType())) {
+ (streamTypeAlias == getUiSoundsStreamType())) {
int ringerMode = getRingerModeInternal();
// do not vibrate if already in vibrate mode
if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
@@ -1183,33 +1167,6 @@
}
}
- /** @see AudioManager#adjustMasterVolume(int, int) */
- public void adjustMasterVolume(int steps, int flags, String callingPackage) {
- adjustMasterVolume(steps, flags, callingPackage, Binder.getCallingUid());
- }
-
- public void adjustMasterVolume(int steps, int flags, String callingPackage, int uid) {
- if (mUseFixedVolume) {
- return;
- }
- if (isMuteAdjust(steps)) {
- setMasterMuteInternal(steps, flags, callingPackage, uid);
- return;
- }
- ensureValidSteps(steps);
- int volume = Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
- int delta = 0;
- int numSteps = Math.abs(steps);
- int direction = steps > 0 ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
- for (int i = 0; i < numSteps; ++i) {
- delta = findVolumeDelta(direction, volume);
- volume += delta;
- }
-
- //Log.d(TAG, "adjustMasterVolume volume: " + volume + " steps: " + steps);
- setMasterVolume(volume, flags, callingPackage, uid);
- }
-
// StreamVolumeCommand contains the information needed to defer the process of
// setStreamVolume() in case the user has to acknowledge the safe volume warning message.
class StreamVolumeCommand {
@@ -1235,9 +1192,9 @@
private void onSetStreamVolume(int streamType, int index, int flags, int device) {
setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false);
- // setting volume on master stream type also controls silent mode
+ // setting volume on ui sounds stream type also controls silent mode
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
- (mStreamVolumeAlias[streamType] == getMasterStreamType())) {
+ (mStreamVolumeAlias[streamType] == getUiSoundsStreamType())) {
int newRingerMode;
if (index == 0) {
newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE
@@ -1381,41 +1338,6 @@
}
}
- private int findVolumeDelta(int direction, int volume) {
- int delta = 0;
- if (direction == AudioManager.ADJUST_RAISE) {
- if (volume == MAX_MASTER_VOLUME) {
- return 0;
- }
- // This is the default value if we make it to the end
- delta = mMasterVolumeRamp[1];
- // If we're raising the volume move down the ramp array until we
- // find the volume we're above and use that groups delta.
- for (int i = mMasterVolumeRamp.length - 1; i > 1; i -= 2) {
- if (volume >= mMasterVolumeRamp[i - 1]) {
- delta = mMasterVolumeRamp[i];
- break;
- }
- }
- } else if (direction == AudioManager.ADJUST_LOWER){
- if (volume == 0) {
- return 0;
- }
- int length = mMasterVolumeRamp.length;
- // This is the default value if we make it to the end
- delta = -mMasterVolumeRamp[length - 1];
- // If we're lowering the volume move up the ramp array until we
- // find the volume we're below and use the group below it's delta
- for (int i = 2; i < length; i += 2) {
- if (volume <= mMasterVolumeRamp[i]) {
- delta = -mMasterVolumeRamp[i - 1];
- break;
- }
- }
- }
- return delta;
- }
-
private void sendBroadcastToAll(Intent intent) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -1464,16 +1386,6 @@
}
// UI update and Broadcast Intent
- private void sendMasterVolumeUpdate(int flags, int oldVolume, int newVolume) {
- mVolumeController.postMasterVolumeChanged(updateFlagsForSystemAudio(flags));
-
- Intent intent = new Intent(AudioManager.MASTER_VOLUME_CHANGED_ACTION);
- intent.putExtra(AudioManager.EXTRA_PREV_MASTER_VOLUME_VALUE, oldVolume);
- intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_VALUE, newVolume);
- sendBroadcastToAll(intent);
- }
-
- // UI update and Broadcast Intent
private void sendMasterMuteUpdate(boolean muted, int flags) {
mVolumeController.postMasterMuteChanged(updateFlagsForSystemAudio(flags));
broadcastMasterMuteStatus(muted);
@@ -1641,27 +1553,21 @@
}
}
- private void setMasterMuteInternal(int adjust, int flags, String callingPackage, int uid) {
+ private void setMasterMuteInternal(boolean mute, int flags, String callingPackage, int uid) {
if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
!= AppOpsManager.MODE_ALLOWED) {
return;
}
- boolean state;
- if (adjust == AudioManager.ADJUST_TOGGLE_MUTE) {
- state = !AudioSystem.getMasterMute();
- } else {
- state = adjust == AudioManager.ADJUST_MUTE;
- }
- if (state != AudioSystem.getMasterMute()) {
- setSystemAudioMute(state);
- AudioSystem.setMasterMute(state);
+ if (mute != AudioSystem.getMasterMute()) {
+ setSystemAudioMute(mute);
+ AudioSystem.setMasterMute(mute);
// Post a persist master volume msg
- sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, state ? 1
+ sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, mute ? 1
: 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY);
- sendMasterMuteUpdate(state, flags);
+ sendMasterMuteUpdate(mute, flags);
Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
- intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, state);
+ intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, mute);
sendBroadcastToAll(intent);
}
}
@@ -1671,6 +1577,10 @@
return AudioSystem.getMasterMute();
}
+ public void setMasterMute(boolean mute, int flags, String callingPackage) {
+ setMasterMuteInternal(mute, flags, callingPackage, Binder.getCallingUid());
+ }
+
protected static int getMaxStreamVolume(int streamType) {
return MAX_STREAM_VOLUME[streamType];
}
@@ -1694,63 +1604,12 @@
}
}
- @Override
- public int getMasterVolume() {
- if (isMasterMute()) return 0;
- return getLastAudibleMasterVolume();
- }
-
- @Override
- public void setMasterVolume(int volume, int flags, String callingPackage) {
- setMasterVolume(volume, flags, callingPackage, Binder.getCallingUid());
- }
-
- public void setMasterVolume(int volume, int flags, String callingPackage, int uid) {
- if (mUseFixedVolume) {
- return;
- }
-
- if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
- return;
- }
-
- if (volume < 0) {
- volume = 0;
- } else if (volume > MAX_MASTER_VOLUME) {
- volume = MAX_MASTER_VOLUME;
- }
- doSetMasterVolume((float)volume / MAX_MASTER_VOLUME, flags);
- }
-
- private void doSetMasterVolume(float volume, int flags) {
- // don't allow changing master volume when muted
- if (!AudioSystem.getMasterMute()) {
- int oldVolume = getMasterVolume();
- AudioSystem.setMasterVolume(volume);
-
- int newVolume = getMasterVolume();
- if (newVolume != oldVolume) {
- // Post a persist master volume msg
- sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME, SENDMSG_REPLACE,
- Math.round(volume * (float)1000.0), 0, null, PERSIST_DELAY);
- setSystemAudioVolume(oldVolume, newVolume, getMasterMaxVolume(), flags);
- }
- // Send the volume update regardless whether there was a change.
- sendMasterVolumeUpdate(flags, oldVolume, newVolume);
- }
- }
-
/** @see AudioManager#getStreamMaxVolume(int) */
public int getStreamMaxVolume(int streamType) {
ensureValidStreamType(streamType);
return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
}
- public int getMasterMaxVolume() {
- return MAX_MASTER_VOLUME;
- }
-
/** Get last audible volume before stream was muted. */
public int getLastAudibleStreamVolume(int streamType) {
ensureValidStreamType(streamType);
@@ -1758,13 +1617,8 @@
return (mStreamStates[streamType].getIndex(device) + 5) / 10;
}
- /** Get last audible master volume before it was muted. */
- public int getLastAudibleMasterVolume() {
- return Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
- }
-
- /** @see AudioManager#getMasterStreamType() */
- public int getMasterStreamType() {
+ /** @see AudioManager#getUiSoundsStreamType() */
+ public int getUiSoundsStreamType() {
return mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM];
}
@@ -1932,20 +1786,6 @@
}
}
- private void restoreMasterVolume() {
- if (mUseFixedVolume) {
- AudioSystem.setMasterVolume(1.0f);
- return;
- }
- if (mUseMasterVolume) {
- float volume = Settings.System.getFloatForUser(mContentResolver,
- Settings.System.VOLUME_MASTER, -1.0f, UserHandle.USER_CURRENT);
- if (volume >= 0.0f) {
- AudioSystem.setMasterVolume(volume);
- }
- }
- }
-
/** @see AudioManager#shouldVibrate(int) */
public boolean shouldVibrate(int vibrateType) {
if (!mHasVibrator) return false;
@@ -3510,9 +3350,8 @@
public void readSettings() {
synchronized (VolumeStreamState.class) {
- // force maximum volume on all streams if fixed volume property
- // or master volume property is set
- if (mUseFixedVolume || mUseMasterVolume) {
+ // force maximum volume on all streams if fixed volume property is set
+ if (mUseFixedVolume) {
mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
return;
}
@@ -3747,7 +3586,7 @@
private int getValidIndex(int index) {
if (index < 0) {
return 0;
- } else if (mUseFixedVolume || mUseMasterVolume || index > mIndexMax) {
+ } else if (mUseFixedVolume || index > mIndexMax) {
return mIndexMax;
}
@@ -4119,16 +3958,6 @@
persistVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
- case MSG_PERSIST_MASTER_VOLUME:
- if (mUseFixedVolume) {
- return;
- }
- Settings.System.putFloatForUser(mContentResolver,
- Settings.System.VOLUME_MASTER,
- msg.arg1 / (float)1000.0,
- UserHandle.USER_CURRENT);
- break;
-
case MSG_PERSIST_MASTER_VOLUME_MUTE:
if (mUseFixedVolume) {
return;
@@ -4197,9 +4026,6 @@
// Restore ringer mode
setRingerModeInt(getRingerModeInternal(), false);
- // Restore master volume
- restoreMasterVolume();
-
// Reset device orientation (if monitored for this device)
if (mMonitorOrientation) {
setOrientationForAudioSystem();
@@ -5666,16 +5492,6 @@
}
}
- public void postMasterVolumeChanged(int flags) {
- if (mController == null)
- return;
- try {
- mController.masterVolumeChanged(flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling masterVolumeChanged", e);
- }
- }
-
public void postMasterMuteChanged(int flags) {
if (mController == null)
return;
@@ -5741,12 +5557,6 @@
}
@Override
- public void adjustMasterVolumeForUid(int steps, int flags, String callingPackage,
- int uid) {
- adjustMasterVolume(steps, flags, callingPackage, uid);
- }
-
- @Override
public int getRingerModeInternal() {
return AudioService.this.getRingerModeInternal();
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 82c99f78..7c93e56 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -202,7 +202,6 @@
reason != HdmiControlService.INITIATED_BY_BOOT_UP);
mLocalDeviceAddresses = initLocalDeviceAddresses();
launchDeviceDiscovery();
- startQueuedActions();
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 3345e46..5363968 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -90,14 +90,12 @@
private final SessionStub mSession;
private final SessionCb mSessionCb;
private final MediaSessionService mService;
- private final boolean mUseMasterVolume;
private final Object mLock = new Object();
private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
new ArrayList<ISessionControllerCallback>();
private long mFlags;
- private IMediaRouter mMediaRouter;
private PendingIntent mMediaButtonReceiver;
private PendingIntent mLaunchIntent;
@@ -141,8 +139,6 @@
mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE);
mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
- mUseMasterVolume = service.getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
}
/**
@@ -247,13 +243,6 @@
flags &= ~AudioManager.FLAG_PLAY_SOUND;
}
if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
- if (mUseMasterVolume) {
- // If this device only uses master volume and playback is local
- // just adjust the master volume and return.
- mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName,
- uid);
- return;
- }
int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs);
if (useSuggested) {
if (AudioSystem.isStreamActive(stream, 0)) {
@@ -729,7 +718,6 @@
@Override
public void setMediaRouter(IMediaRouter router) {
- mMediaRouter = router;
mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 4383bb1..72205d6 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -89,11 +89,9 @@
private final Object mLock = new Object();
private final MessageHandler mHandler = new MessageHandler();
private final PowerManager.WakeLock mMediaEventWakeLock;
- private final boolean mUseMasterVolume;
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
- private AudioManager mAudioManager;
private AudioManagerInternal mAudioManagerInternal;
private ContentResolver mContentResolver;
private SettingsObserver mSettingsObserver;
@@ -110,8 +108,6 @@
mPriorityStack = new MediaSessionStack();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
- mUseMasterVolume = context.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
}
@Override
@@ -121,7 +117,6 @@
mKeyguardManager =
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
mAudioService = getAudioService();
- mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
mContentResolver = getContext().getContentResolver();
mSettingsObserver = new SettingsObserver();
@@ -468,11 +463,6 @@
return -1;
}
- private boolean isSessionDiscoverable(MediaSessionRecord record) {
- // TODO probably want to check more than if it's active.
- return record.isActive();
- }
-
private void pushSessionsChanged(int userId) {
synchronized (mLock) {
List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
@@ -889,12 +879,8 @@
}
try {
String packageName = getContext().getOpPackageName();
- if (mUseMasterVolume) {
- mAudioService.adjustMasterVolume(direction, flags, packageName);
- } else {
- mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
- flags, packageName);
- }
+ mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
+ flags, packageName);
} catch (RemoteException e) {
Log.e(TAG, "Error adjusting default volume.", e);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9b6b5fb..bedcabe 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2584,8 +2584,8 @@
@Override
public void run() {
String listenerName = listener == null ? null : listener.component.toShortString();
- EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag, userId,
- mustHaveFlags, mustNotHaveFlags, reason, listenerName);
+ if (DBG) EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag,
+ userId, mustHaveFlags, mustNotHaveFlags, reason, listenerName);
synchronized (mNotificationList) {
int index = indexOfNotificationLocked(pkg, tag, id, userId);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9746142..1e64017 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -380,7 +380,6 @@
boolean mHasSoftInput = false;
boolean mTranslucentDecorEnabled = true;
boolean mUseTvRouting;
- boolean mUseMasterVolume;
int mPointerLocationMode = 0; // guarded by mLock
@@ -1270,8 +1269,6 @@
com.android.internal.R.integer.config_triplePressOnPowerBehavior);
mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION;
- mUseMasterVolume = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
readConfigurationDependentBehaviors();
@@ -4859,26 +4856,16 @@
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
try {
- if (mUseMasterVolume) {
- getAudioService().adjustMasterVolume(AudioManager.ADJUST_RAISE, flags,
- pkgName);
- } else {
- getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
- AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
- }
+ getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
} catch (RemoteException e) {
Log.e(TAG, "Error dispatching volume up in dispatchTvAudioEvent.", e);
}
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
try {
- if (mUseMasterVolume) {
- getAudioService().adjustMasterVolume(AudioManager.ADJUST_LOWER, flags,
- pkgName);
- } else {
- getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
- AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
- }
+ getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
} catch (RemoteException e) {
Log.e(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e);
}
@@ -4886,14 +4873,9 @@
case KeyEvent.KEYCODE_VOLUME_MUTE:
try {
if (event.getRepeatCount() == 0) {
- if (mUseMasterVolume) {
- getAudioService().adjustMasterVolume(AudioManager.ADJUST_TOGGLE_MUTE,
- flags, pkgName);
- } else {
- getAudioService().adjustSuggestedStreamVolume(
- AudioManager.ADJUST_TOGGLE_MUTE,
- AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
- }
+ getAudioService().adjustSuggestedStreamVolume(
+ AudioManager.ADJUST_TOGGLE_MUTE,
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName);
}
} catch (RemoteException e) {
Log.e(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e);
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index e57396f..ac8ad30 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -102,7 +102,6 @@
};
private int mCurrentIndex = 0;
private int mCurrentMaxIndex = 0;
- private final boolean mUseMasterVolume;
// TODO: Should handle STANDBY case.
private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
@@ -117,8 +116,6 @@
mContext = context;
mListener = listener;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- mUseMasterVolume = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
mHal.init();
}
@@ -139,12 +136,10 @@
} else {
Slog.w(TAG, "HdmiControlService is not available");
}
- if (!mUseMasterVolume) {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
- filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
- mContext.registerReceiver(mVolumeReceiver, filter);
- }
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
+ filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+ mContext.registerReceiver(mVolumeReceiver, filter);
updateVolume();
}
}
@@ -545,7 +540,7 @@
}
private float getMediaStreamVolume() {
- return mUseMasterVolume ? 1.0f : ((float) mCurrentIndex / (float) mCurrentMaxIndex);
+ return (float) mCurrentIndex / (float) mCurrentMaxIndex;
}
private class Connection implements IBinder.DeathRecipient {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
index 57ed876..ea59d4b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java
@@ -53,6 +53,7 @@
private static final String DEVICE_OWNER_XML = "device_owner.xml";
private static final String TAG_DEVICE_OWNER = "device-owner";
+ private static final String TAG_DEVICE_INITIALIZER = "device-initializer";
private static final String TAG_PROFILE_OWNER = "profile-owner";
private static final String ATTR_NAME = "name";
private static final String ATTR_PACKAGE = "package";
@@ -68,6 +69,9 @@
// Internal state for the device owner package.
private OwnerInfo mDeviceOwner;
+ // Internal state for the device initializer package.
+ private OwnerInfo mDeviceInitializer;
+
// Internal state for the profile owner packages.
private final HashMap<Integer, OwnerInfo> mProfileOwners = new HashMap<Integer, OwnerInfo>();
@@ -104,6 +108,15 @@
}
/**
+ * Creates an instance of the device owner object with the device initializer set.
+ */
+ static DeviceOwner createWithDeviceInitializer(String packageName, String ownerName) {
+ DeviceOwner owner = new DeviceOwner();
+ owner.mDeviceInitializer = new OwnerInfo(ownerName, packageName);
+ return owner;
+ }
+
+ /**
* Creates an instance of the device owner object with the profile owner set.
*/
static DeviceOwner createWithProfileOwner(ComponentName admin, String ownerName, int userId) {
@@ -128,6 +141,26 @@
mDeviceOwner = null;
}
+ String getDeviceInitializerPackageName() {
+ return mDeviceInitializer != null ? mDeviceInitializer.packageName : null;
+ }
+
+ String getDeviceInitializerName() {
+ return mDeviceInitializer != null ? mDeviceInitializer.name : null;
+ }
+
+ void setDeviceInitializer(String packageName, String ownerName) {
+ mDeviceInitializer = new OwnerInfo(ownerName, packageName);
+ }
+
+ void clearDeviceInitializer() {
+ mDeviceInitializer = null;
+ }
+
+ boolean hasDeviceInitializer() {
+ return mDeviceInitializer != null;
+ }
+
void setProfileOwner(ComponentName admin, String ownerName, int userId) {
mProfileOwners.put(userId, new OwnerInfo(ownerName, admin));
}
@@ -199,6 +232,10 @@
String name = parser.getAttributeValue(null, ATTR_NAME);
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
mDeviceOwner = new OwnerInfo(name, packageName);
+ } else if (tag.equals(TAG_DEVICE_INITIALIZER)) {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+ mDeviceInitializer = new OwnerInfo(name, packageName);
} else if (tag.equals(TAG_PROFILE_OWNER)) {
String profileOwnerPackageName = parser.getAttributeValue(null, ATTR_PACKAGE);
String profileOwnerName = parser.getAttributeValue(null, ATTR_NAME);
@@ -259,6 +296,16 @@
out.endTag(null, TAG_DEVICE_OWNER);
}
+ // Write device initializer tag
+ if (mDeviceInitializer != null) {
+ out.startTag(null, TAG_DEVICE_INITIALIZER);
+ out.attribute(null, ATTR_PACKAGE, mDeviceInitializer.packageName);
+ if (mDeviceInitializer.name != null) {
+ out.attribute(null, ATTR_NAME, mDeviceInitializer.name);
+ }
+ out.endTag(null, TAG_DEVICE_INITIALIZER);
+ }
+
// Write profile owner tags
if (mProfileOwners.size() > 0) {
for (HashMap.Entry<Integer, OwnerInfo> owner : mProfileOwners.entrySet()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a19cd9f..fc9ff76 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1139,13 +1139,15 @@
boolean ownsProfile = (getProfileOwner(userHandle) != null
&& getProfileOwner(userHandle).getPackageName()
.equals(admin.info.getPackageName()));
+ boolean ownsInitialization = isDeviceInitializer(admin.info.getPackageName())
+ && !hasUserSetupCompleted(userHandle);
if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
- if (ownsDevice) {
+ if (ownsDevice || (userHandle == UserHandle.USER_OWNER && ownsInitialization)) {
return admin;
}
} else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
- if (ownsDevice || ownsProfile) {
+ if (ownsDevice || ownsProfile || ownsInitialization) {
return admin;
}
} else {
@@ -1899,7 +1901,7 @@
return;
}
if (admin.getUid() != Binder.getCallingUid()) {
- // If trying to remove device owner, refuse when the caller is not the owner.
+ // Active device owners must remain active admins.
if (isDeviceOwner(adminReceiver.getPackageName())) {
return;
}
@@ -3866,6 +3868,110 @@
}
@Override
+ public boolean setDeviceInitializer(ComponentName who, ComponentName initializer,
+ String ownerName) {
+ if (!mHasFeature) {
+ return false;
+ }
+ if (initializer == null || !DeviceOwner.isInstalled(
+ initializer.getPackageName(), mContext.getPackageManager())) {
+ throw new IllegalArgumentException("Invalid component name " + initializer
+ + " for device initializer");
+ }
+ synchronized (this) {
+ enforceCanSetDeviceInitializer(who);
+
+ if (mDeviceOwner != null && mDeviceOwner.hasDeviceInitializer()) {
+ throw new IllegalStateException(
+ "Trying to set device initializer but device initializer is already set.");
+ }
+
+ if (mDeviceOwner == null) {
+ // Device owner state does not exist, create it.
+ mDeviceOwner = DeviceOwner.createWithDeviceInitializer(
+ initializer.getPackageName(), ownerName);
+ mDeviceOwner.writeOwnerFile();
+ return true;
+ } else {
+ // Device owner already exists, update it.
+ mDeviceOwner.setDeviceInitializer(initializer.getPackageName(), ownerName);
+ mDeviceOwner.writeOwnerFile();
+ return true;
+ }
+ }
+ }
+
+ private void enforceCanSetDeviceInitializer(ComponentName who) {
+ if (who == null) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_DEVICE_ADMINS, null);
+ if (hasUserSetupCompleted(UserHandle.USER_OWNER)) {
+ throw new IllegalStateException(
+ "Trying to set device initializer but device is already provisioned.");
+ }
+ } else {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+ }
+
+ @Override
+ public boolean isDeviceInitializer(String packageName) {
+ if (!mHasFeature) {
+ return false;
+ }
+ synchronized (this) {
+ return mDeviceOwner != null
+ && mDeviceOwner.hasDeviceInitializer()
+ && mDeviceOwner.getDeviceInitializerPackageName().equals(packageName);
+ }
+ }
+
+ @Override
+ public String getDeviceInitializer() {
+ if (!mHasFeature) {
+ return null;
+ }
+ synchronized (this) {
+ if (mDeviceOwner != null && mDeviceOwner.hasDeviceInitializer()) {
+ return mDeviceOwner.getDeviceInitializerPackageName();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void clearDeviceInitializer(ComponentName who) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who, UserHandle.getCallingUserId());
+
+ if (admin.getUid() != Binder.getCallingUid()) {
+ throw new SecurityException("Admin " + who + " is not owned by uid "
+ + Binder.getCallingUid());
+ }
+
+ if (!isDeviceInitializer(admin.info.getPackageName())
+ && !isDeviceOwner(admin.info.getPackageName())) {
+ throw new SecurityException(
+ "clearDeviceInitializer can only be called by the device initializer/owner");
+ }
+ synchronized (this) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mDeviceOwner != null) {
+ mDeviceOwner.clearDeviceInitializer();
+ mDeviceOwner.writeOwnerFile();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @Override
public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
if (!mHasFeature) {
return false;
@@ -3938,7 +4044,7 @@
Bundle userRestrictions = mUserManager.getUserRestrictions();
mUserManager.setUserRestrictions(new Bundle(), userHandle);
if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) {
- audioManager.adjustMasterVolume(AudioManager.ADJUST_UNMUTE, 0);
+ audioManager.setMasterMute(false, 0);
}
if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) {
audioManager.setMicrophoneMute(false);
@@ -3960,6 +4066,51 @@
}
@Override
+ public boolean setUserEnabled(ComponentName who) {
+ if (!mHasFeature) {
+ return false;
+ }
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ int userId = UserHandle.getCallingUserId();
+
+ ActiveAdmin activeAdmin =
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (!isDeviceInitializer(activeAdmin.info.getPackageName())) {
+ throw new SecurityException(
+ "This method can only be called by device initializers");
+ }
+
+ long id = Binder.clearCallingIdentity();
+ try {
+ if (!isDeviceOwner(activeAdmin.info.getPackageName())) {
+ IPackageManager ipm = AppGlobals.getPackageManager();
+ ipm.setComponentEnabledSetting(who,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP, userId);
+
+ removeActiveAdmin(who, userId);
+ }
+
+ if (userId == UserHandle.USER_OWNER) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 1);
+ }
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 1, userId);
+ } catch (RemoteException e) {
+ Log.i(LOG_TAG, "Can't talk to package manager", e);
+ return false;
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ return true;
+ }
+ }
+
+ @Override
public void setProfileEnabled(ComponentName who) {
if (!mHasFeature) {
return;
@@ -4815,8 +4966,7 @@
if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
iAudioService.setMicrophoneMute(true, who.getPackageName());
} else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- iAudioService.adjustMasterVolume(AudioManager.ADJUST_MUTE, 0,
- who.getPackageName());
+ iAudioService.setMasterMute(true, 0, who.getPackageName());
}
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
@@ -4881,8 +5031,7 @@
if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) {
iAudioService.setMicrophoneMute(false, who.getPackageName());
} else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) {
- iAudioService.adjustMasterVolume(AudioManager.ADJUST_UNMUTE, 0,
- who.getPackageName());
+ iAudioService.setMasterMute(false, 0, who.getPackageName());
}
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed to talk to AudioService.", re);
@@ -5322,10 +5471,8 @@
IAudioService iAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
- try{
- iAudioService.adjustMasterVolume(
- on ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, 0,
- who.getPackageName());
+ try {
+ iAudioService.setMasterMute(on, 0, who.getPackageName());
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed to setMasterMute", re);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index cd6e786..a0c7f86 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -669,7 +669,8 @@
mSystemServiceManager.startService("com.android.server.wifi.RttService");
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET)) {
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
}
diff --git a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
index b3900b9..8849acd 100644
--- a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java
@@ -96,7 +96,7 @@
}
try {
listenToSocket();
- } catch (IOException e) {
+ } catch (Exception e) {
/* Don't loop too fast if adbd dies, before init restarts it */
SystemClock.sleep(1000);
}
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index e17abc0..a6e9677 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceServer;
+import android.media.midi.MidiDispatcher;
import android.media.midi.MidiManager;
import android.media.midi.MidiPort;
import android.media.midi.MidiReceiver;
@@ -30,6 +31,8 @@
import android.system.StructPollfd;
import android.util.Log;
+import libcore.io.IoUtils;
+
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -39,8 +42,9 @@
public final class UsbMidiDevice implements Closeable {
private static final String TAG = "UsbMidiDevice";
- private final MidiDeviceServer mServer;
- private final MidiReceiver[] mOutputPortReceivers;
+ private MidiDeviceServer mServer;
+
+ private final MidiReceiver[] mInputPortReceivers;
// for polling multiple FileDescriptors for MIDI events
private final StructPollfd[] mPollFDs;
@@ -50,12 +54,6 @@
private final FileOutputStream[] mOutputStreams;
public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
- MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
- if (midiManager == null) {
- Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
- return null;
- }
-
// FIXME - support devices with different number of input and output ports
int subDevices = nativeGetSubdeviceCount(card, device);
if (subDevices <= 0) {
@@ -70,19 +68,16 @@
return null;
}
- MidiDeviceServer server = midiManager.createDeviceServer(subDevices, subDevices, properties,
- false, MidiDeviceInfo.TYPE_USB);
- if (server == null) {
+ UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors, fileDescriptors);
+ if (!midiDevice.register(context, properties)) {
+ IoUtils.closeQuietly(midiDevice);
Log.e(TAG, "createDeviceServer failed");
return null;
}
-
- return new UsbMidiDevice(server, fileDescriptors, fileDescriptors);
+ return midiDevice;
}
- private UsbMidiDevice(MidiDeviceServer server, FileDescriptor[] inputFiles,
- FileDescriptor[] outputFiles) {
- mServer = server;
+ private UsbMidiDevice(FileDescriptor[] inputFiles, FileDescriptor[] outputFiles) {
int inputCount = inputFiles.length;
int outputCount = outputFiles.length;
@@ -102,26 +97,36 @@
mOutputStreams[i] = new FileOutputStream(outputFiles[i]);
}
- mOutputPortReceivers = new MidiReceiver[outputCount];
- for (int port = 0; port < outputCount; port++) {
- mOutputPortReceivers[port] = server.openOutputPortReceiver(port);
- }
-
+ mInputPortReceivers = new MidiReceiver[inputCount];
for (int port = 0; port < inputCount; port++) {
- final int portNumberF = port;
- MidiReceiver receiver = new MidiReceiver() {
-
+ final int portF = port;
+ mInputPortReceivers[port] = new MidiReceiver() {
@Override
public void post(byte[] data, int offset, int count, long timestamp)
throws IOException {
// FIXME - timestamps are ignored, future posting not supported yet.
- mOutputStreams[portNumberF].write(data, offset, count);
+ mOutputStreams[portF].write(data, offset, count);
}
};
- MidiSender sender = server.openInputPortSender(port);
- sender.connect(receiver);
+ }
+ }
+
+ private boolean register(Context context, Bundle properties) {
+ MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+ if (midiManager == null) {
+ Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
+ return false;
}
+ int outputCount = mOutputStreams.length;
+ mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount,
+ properties, MidiDeviceInfo.TYPE_USB);
+ if (mServer == null) {
+ return false;
+ }
+ final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
+
+ // FIXME can we only run this when we have a dispatcher that has listeners?
new Thread() {
@Override
public void run() {
@@ -137,8 +142,8 @@
pfd.revents = 0;
int count = mInputStreams[index].read(buffer);
- mOutputPortReceivers[index].post(buffer, 0, count,
- System.nanoTime());
+ long timestamp = System.nanoTime();
+ outputReceivers[index].post(buffer, 0, count, timestamp);
} else if ((pfd.revents & (OsConstants.POLLERR
| OsConstants.POLLHUP)) != 0) {
done = true;
@@ -155,11 +160,15 @@
}
}
}.start();
+
+ return true;
}
@Override
public void close() throws IOException {
- mServer.close();
+ if (mServer != null) {
+ mServer.close();
+ }
for (int i = 0; i < mInputStreams.length; i++) {
mInputStreams[i].close();
diff --git a/test-runner/src/android/test/RenamingDelegatingContext.java b/test-runner/src/android/test/RenamingDelegatingContext.java
index 3d763c7..3c4da9e 100644
--- a/test-runner/src/android/test/RenamingDelegatingContext.java
+++ b/test-runner/src/android/test/RenamingDelegatingContext.java
@@ -107,7 +107,7 @@
}
/**
- * @param context : the context that will be delagated.
+ * @param context : the context that will be delegated.
* @param filePrefix : a prefix with which database and file names will be
* prefixed.
*/
@@ -118,8 +118,8 @@
}
/**
- * @param context : the context that will be delagated.
- * @param fileContext : the context that file and db methods will be delgated to
+ * @param context : the context that will be delegated.
+ * @param fileContext : the context that file and db methods will be delegated to
* @param filePrefix : a prefix with which database and file names will be
* prefixed.
*/
diff --git a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
index 38846bd..a0db7bf 100644
--- a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
@@ -65,6 +65,9 @@
@SuppressWarnings({"SuspiciousNameCombination", "UnnecessaryLocalVariable"}) // Imported code
public static BufferedImage createDropShadow(BufferedImage source, int shadowSize,
float shadowOpacity, int shadowRgb) {
+ if (shadowSize == 0) {
+ return source;
+ }
// This code is based on
// http://www.jroller.com/gfx/entry/non_rectangular_shadow
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 4d2c2fc..c6d60f8 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -181,7 +181,7 @@
*/
private static LayoutLog sCurrentLog = sDefaultLog;
- private static final int LAST_SUPPORTED_FEATURE = Features.PREFERENCES_RENDERING;
+ private static final int LAST_SUPPORTED_FEATURE = Features.RENDER_ALL_DRAWABLE_STATES;
@Override
public int getApiLevel() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
new file mode 100644
index 0000000..cfc8f40
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.android;
+
+import com.android.ide.common.rendering.api.SessionParams.Key;
+
+/**
+ * This contains all known keys for the {@link RenderParams#getFlag(Key)}.
+ * <p/>
+ * The IDE has its own copy of this class which may be newer or older than this one.
+ * <p/>
+ * Constants should never be modified or removed from this class.
+ */
+public final class RenderParamsFlags {
+
+ public static final Key<String> FLAG_KEY_ROOT_TAG =
+ new Key<String>("rootTag", String.class);
+ public static final Key<Boolean> FLAG_KEY_DISABLE_BITMAP_CACHING =
+ new Key<Boolean>("disableBitmapCaching", Boolean.class);
+ public static final Key<Boolean> FLAG_KEY_RENDER_ALL_DRAWABLE_STATES =
+ new Key<Boolean>("renderAllDrawableStates", Boolean.class);
+
+ // Disallow instances.
+ private RenderParamsFlags() {}
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/SessionParamsFlags.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/SessionParamsFlags.java
deleted file mode 100644
index 51a0104..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/SessionParamsFlags.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.android;
-
-import com.android.ide.common.rendering.api.SessionParams;
-
-/**
- * This contains all known keys for the {@link SessionParams#getFlag(SessionParams.Key)}.
- * <p/>
- * The IDE has its own copy of this class which may be newer or older than this one.
- * <p/>
- * Constants should never be modified or removed from this class.
- */
-public final class SessionParamsFlags {
-
- public static final SessionParams.Key<String> FLAG_KEY_ROOT_TAG =
- new SessionParams.Key<String>("rootTag", String.class);
- public static final SessionParams.Key<Boolean> FLAG_KEY_DISABLE_BITMAP_CACHING =
- new SessionParams.Key<Boolean>("disableBitmapCaching", Boolean.class);
-
- // Disallow instances.
- private SessionParamsFlags() {}
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
index 669e6b5..3dee1e2 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
@@ -16,20 +16,20 @@
package com.android.layoutlib.bridge.impl;
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
-
import com.android.ide.common.rendering.api.DrawableParams;
import com.android.ide.common.rendering.api.HardwareConfig;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.Result.Status;
import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.resources.ResourceType;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
import android.view.AttachInfo_Accessor;
import android.view.View.MeasureSpec;
import android.widget.FrameLayout;
@@ -38,7 +38,9 @@
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
-import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Action to render a given Drawable provided through {@link DrawableParams#getDrawable()}.
@@ -71,11 +73,37 @@
return Status.ERROR_NOT_A_DRAWABLE.createResult();
}
+ Drawable d = ResourceHelper.getDrawable(drawableResource, context);
+
+ final Boolean allStates =
+ params.getFlag(RenderParamsFlags.FLAG_KEY_RENDER_ALL_DRAWABLE_STATES);
+ if (allStates == Boolean.TRUE) {
+ final List<BufferedImage> result;
+
+ if (d instanceof StateListDrawable) {
+ result = new ArrayList<BufferedImage>();
+ final StateListDrawable stateList = (StateListDrawable) d;
+ for (int i = 0; i < stateList.getStateCount(); i++) {
+ final Drawable stateDrawable = stateList.getStateDrawable(i);
+ result.add(renderImage(hardwareConfig, stateDrawable, context));
+ }
+ } else {
+ result = Collections.singletonList(renderImage(hardwareConfig, d, context));
+ }
+
+ return Status.SUCCESS.createResult(result);
+ } else {
+ BufferedImage image = renderImage(hardwareConfig, d, context);
+ return Status.SUCCESS.createResult(image);
+ }
+ }
+
+ private BufferedImage renderImage(HardwareConfig hardwareConfig, Drawable d,
+ BridgeContext context) {
// create a simple FrameLayout
FrameLayout content = new FrameLayout(context);
// get the actual Drawable object to draw
- Drawable d = ResourceHelper.getDrawable(drawableResource, context);
content.setBackground(d);
// set the AttachInfo on the root view.
@@ -83,8 +111,27 @@
// measure
- int w = hardwareConfig.getScreenWidth();
- int h = hardwareConfig.getScreenHeight();
+ int w = d.getIntrinsicWidth();
+ int h = d.getIntrinsicHeight();
+
+ final int screenWidth = hardwareConfig.getScreenWidth();
+ final int screenHeight = hardwareConfig.getScreenHeight();
+
+ if (w == -1 || h == -1) {
+ // Use screen size when either intrinsic width or height isn't available
+ w = screenWidth;
+ h = screenHeight;
+ } else if (w > screenWidth || h > screenHeight) {
+ // If image wouldn't fit to the screen, resize it to avoid cropping.
+
+ // We need to find scale such that scale * w <= screenWidth, scale * h <= screenHeight
+ double scale = Math.min((double) screenWidth / w, (double) screenHeight / h);
+
+ // scale * w / scale * h = w / h, so, proportions are preserved.
+ w = (int) Math.floor(scale * w);
+ h = (int) Math.floor(scale * h);
+ }
+
int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
content.measure(w_spec, h_spec);
@@ -108,8 +155,7 @@
// and draw
content.draw(canvas);
-
- return Status.SUCCESS.createResult(image);
+ return image;
}
protected BufferedImage getImage(int w, int h) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 875cc87..607563a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -50,7 +50,7 @@
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.android.SessionParamsFlags;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.bars.BridgeActionBar;
import com.android.layoutlib.bridge.bars.AppCompatActionBar;
import com.android.layoutlib.bridge.bars.Config;
@@ -400,7 +400,7 @@
// it can instantiate the custom Fragment.
Fragment_Delegate.setProjectCallback(params.getProjectCallback());
- String rootTag = params.getFlag(SessionParamsFlags.FLAG_KEY_ROOT_TAG);
+ String rootTag = params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG);
boolean isPreference = "PreferenceScreen".equals(rootTag);
View view;
if (isPreference) {
@@ -557,7 +557,7 @@
// This is useful when mImage is just a wrapper of Graphics2D so
// it doesn't get cached.
boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag(
- SessionParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING));
+ RenderParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING));
if (newRenderSize || mCanvas == null || disableBitmapCaching) {
if (params.getImageFactory() != null) {
mImage = params.getImageFactory().getImage(