am 7f3cf449: Merge "docs: update screen support doc to de-emphasize support for 1.5 this includes a variety of other revisions to reorganize some of the content in the main document and also add separate documents for how to enable screen filtering and add screen supp
* commit '7f3cf449fe1b90b902a37ddc3c05ec7aa236e584':
docs: update screen support doc to de-emphasize support for 1.5 this includes a variety of other revisions to reorganize some of the content in the main document and also add separate documents for how to enable screen filtering and add screen support on 1.5
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index b073004..5c8abe4 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -106,6 +106,8 @@
runDumpHeap();
} else if (op.equals("monitor")) {
runMonitor();
+ } else if (op.equals("screen-compat")) {
+ runScreenCompat();
} else {
throw new IllegalArgumentException("Unknown command: " + op);
}
@@ -776,6 +778,29 @@
controller.run();
}
+ private void runScreenCompat() throws Exception {
+ String mode = nextArgRequired();
+ boolean enabled;
+ if ("on".equals(mode)) {
+ enabled = true;
+ } else if ("off".equals(mode)) {
+ enabled = false;
+ } else {
+ System.err.println("Error: enabled mode must be 'on' or 'off' at " + mode);
+ showUsage();
+ return;
+ }
+
+ String packageName = nextArgRequired();
+ do {
+ try {
+ mAm.setPackageScreenCompatMode(packageName, enabled);
+ } catch (RemoteException e) {
+ }
+ packageName = nextArg();
+ } while (packageName != null);
+ }
+
private class IntentReceiver extends IIntentReceiver.Stub {
private boolean mFinished = false;
@@ -956,6 +981,8 @@
" start monitoring: am monitor [--gdb <port>]\n" +
" --gdb: start gdbserv on the given port at crash/ANR\n" +
"\n" +
+ " control screen compatibility: am screen-compat [on|off] [package]\n" +
+ "\n" +
" <INTENT> specifications include these flags:\n" +
" [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
" [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 6426635..11e9975 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1398,6 +1398,16 @@
return true;
}
+ case SET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ String pkg = data.readString();
+ boolean enabled = data.readInt() != 0;
+ setPackageScreenCompatMode(pkg, enabled);
+ reply.writeNoException();
+ return true;
+ }
+
}
return super.onTransact(code, data, reply, flags);
@@ -3142,5 +3152,18 @@
return result;
}
+ public void setPackageScreenCompatMode(String packageName, boolean compatEnabled)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ data.writeInt(compatEnabled ? 1 : 0);
+ mRemote.transact(SET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bd83762..67e8839 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -202,7 +202,7 @@
Bundle mCoreSettings = null;
- private static final class ActivityClientRecord {
+ static final class ActivityClientRecord {
IBinder token;
int ident;
Intent intent;
@@ -220,6 +220,7 @@
ActivityClientRecord nextIdle;
ActivityInfo activityInfo;
+ CompatibilityInfo compatInfo;
LoadedApk packageInfo;
List<ResultInfo> pendingResults;
@@ -260,7 +261,7 @@
}
}
- private final class ProviderClientRecord implements IBinder.DeathRecipient {
+ final class ProviderClientRecord implements IBinder.DeathRecipient {
final String mName;
final IContentProvider mProvider;
final ContentProvider mLocalProvider;
@@ -277,7 +278,7 @@
}
}
- private static final class NewIntentData {
+ static final class NewIntentData {
List<Intent> intents;
IBinder token;
public String toString() {
@@ -285,7 +286,7 @@
}
}
- private static final class ReceiverData extends BroadcastReceiver.PendingResult {
+ static final class ReceiverData extends BroadcastReceiver.PendingResult {
public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
boolean ordered, boolean sticky, IBinder token) {
super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, token);
@@ -294,6 +295,7 @@
Intent intent;
ActivityInfo info;
+ CompatibilityInfo compatInfo;
public String toString() {
return "ReceiverData{intent=" + intent + " packageName=" +
info.packageName + " resultCode=" + getResultCode()
@@ -302,8 +304,9 @@
}
}
- private static final class CreateBackupAgentData {
+ static final class CreateBackupAgentData {
ApplicationInfo appInfo;
+ CompatibilityInfo compatInfo;
int backupMode;
public String toString() {
return "CreateBackupAgentData{appInfo=" + appInfo
@@ -312,9 +315,10 @@
}
}
- private static final class CreateServiceData {
+ static final class CreateServiceData {
IBinder token;
ServiceInfo info;
+ CompatibilityInfo compatInfo;
Intent intent;
public String toString() {
return "CreateServiceData{token=" + token + " className="
@@ -323,7 +327,7 @@
}
}
- private static final class BindServiceData {
+ static final class BindServiceData {
IBinder token;
Intent intent;
boolean rebind;
@@ -332,7 +336,7 @@
}
}
- private static final class ServiceArgsData {
+ static final class ServiceArgsData {
IBinder token;
int startId;
int flags;
@@ -343,7 +347,7 @@
}
}
- private static final class AppBindData {
+ static final class AppBindData {
LoadedApk info;
String processName;
ApplicationInfo appInfo;
@@ -355,13 +359,14 @@
int debugMode;
boolean restrictedBackupMode;
Configuration config;
+ CompatibilityInfo compatInfo;
boolean handlingProfiling;
public String toString() {
return "AppBindData{appInfo=" + appInfo + "}";
}
}
- private static final class DumpComponentInfo {
+ static final class DumpComponentInfo {
FileDescriptor fd;
IBinder token;
String prefix;
@@ -369,7 +374,7 @@
boolean dumped;
}
- private static final class ResultData {
+ static final class ResultData {
IBinder token;
List<ResultInfo> results;
public String toString() {
@@ -377,22 +382,27 @@
}
}
- private static final class ContextCleanupInfo {
+ static final class ContextCleanupInfo {
ContextImpl context;
String what;
String who;
}
- private static final class ProfilerControlData {
+ static final class ProfilerControlData {
String path;
ParcelFileDescriptor fd;
}
- private static final class DumpHeapData {
+ static final class DumpHeapData {
String path;
ParcelFileDescriptor fd;
}
+ static final class UpdateCompatibilityData {
+ String pkg;
+ CompatibilityInfo info;
+ }
+
private final class ApplicationThread extends ApplicationThreadNative {
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
@@ -443,7 +453,8 @@
// we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,
+ ActivityInfo info, CompatibilityInfo compatInfo, Bundle state,
+ List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) {
ActivityClientRecord r = new ActivityClientRecord();
@@ -451,6 +462,7 @@
r.ident = ident;
r.intent = intent;
r.activityInfo = info;
+ r.compatInfo = compatInfo;
r.state = state;
r.pendingResults = pendingResults;
@@ -484,33 +496,40 @@
}
public final void scheduleReceiver(Intent intent, ActivityInfo info,
- int resultCode, String data, Bundle extras, boolean sync) {
+ CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
+ boolean sync) {
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
sync, false, mAppThread.asBinder());
r.info = info;
+ r.compatInfo = compatInfo;
queueOrSendMessage(H.RECEIVER, r);
}
- public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) {
+ public final void scheduleCreateBackupAgent(ApplicationInfo app,
+ CompatibilityInfo compatInfo, int backupMode) {
CreateBackupAgentData d = new CreateBackupAgentData();
d.appInfo = app;
+ d.compatInfo = compatInfo;
d.backupMode = backupMode;
queueOrSendMessage(H.CREATE_BACKUP_AGENT, d);
}
- public final void scheduleDestroyBackupAgent(ApplicationInfo app) {
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app,
+ CompatibilityInfo compatInfo) {
CreateBackupAgentData d = new CreateBackupAgentData();
d.appInfo = app;
+ d.compatInfo = compatInfo;
queueOrSendMessage(H.DESTROY_BACKUP_AGENT, d);
}
public final void scheduleCreateService(IBinder token,
- ServiceInfo info) {
+ ServiceInfo info, CompatibilityInfo compatInfo) {
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
+ s.compatInfo = compatInfo;
queueOrSendMessage(H.CREATE_SERVICE, s);
}
@@ -553,7 +572,8 @@
ComponentName instrumentationName, String profileFile,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
int debugMode, boolean isRestrictedBackupMode, Configuration config,
- Map<String, IBinder> services, Bundle coreSettings) {
+ CompatibilityInfo compatInfo, Map<String, IBinder> services,
+ Bundle coreSettings) {
if (services != null) {
// Setup the service cache in the ServiceManager
@@ -573,6 +593,7 @@
data.debugMode = debugMode;
data.restrictedBackupMode = isRestrictedBackupMode;
data.config = config;
+ data.compatInfo = compatInfo;
queueOrSendMessage(H.BIND_APPLICATION, data);
}
@@ -903,6 +924,13 @@
public void setCoreSettings(Bundle coreSettings) {
queueOrSendMessage(H.SET_CORE_SETTINGS, coreSettings);
}
+
+ public void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) {
+ UpdateCompatibilityData ucd = new UpdateCompatibilityData();
+ ucd.pkg = pkg;
+ ucd.info = info;
+ queueOrSendMessage(H.UPDATE_PACKAGE_COMPATIBILITY_INFO, ucd);
+ }
}
private final class H extends Handler {
@@ -945,6 +973,7 @@
public static final int DUMP_ACTIVITY = 136;
public static final int SLEEPING = 137;
public static final int SET_CORE_SETTINGS = 138;
+ public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
@@ -987,6 +1016,7 @@
case DUMP_ACTIVITY: return "DUMP_ACTIVITY";
case SLEEPING: return "SLEEPING";
case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS";
+ case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
}
}
return "(unknown)";
@@ -998,7 +1028,7 @@
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
r.packageInfo = getPackageInfoNoCheck(
- r.activityInfo.applicationInfo);
+ r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
} break;
case RELAUNCH_ACTIVITY: {
@@ -1072,7 +1102,7 @@
handleRequestThumbnail((IBinder)msg.obj);
break;
case CONFIGURATION_CHANGED:
- handleConfigurationChanged((Configuration)msg.obj);
+ handleConfigurationChanged((Configuration)msg.obj, null);
break;
case CLEAN_UP_CONTEXT:
ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
@@ -1125,6 +1155,8 @@
case SET_CORE_SETTINGS:
handleSetCoreSettings((Bundle) msg.obj);
break;
+ case UPDATE_PACKAGE_COMPATIBILITY_INFO:
+ handleUpdatePackageCompatibilityInfo((UpdateCompatibilityData)msg.obj);
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what);
}
@@ -1335,7 +1367,8 @@
return mH;
}
- public final LoadedApk getPackageInfo(String packageName, int flags) {
+ public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
+ int flags) {
synchronized (mPackages) {
WeakReference<LoadedApk> ref;
if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) {
@@ -1369,13 +1402,14 @@
}
if (ai != null) {
- return getPackageInfo(ai, flags);
+ return getPackageInfo(ai, compatInfo, flags);
}
return null;
}
- public final LoadedApk getPackageInfo(ApplicationInfo ai, int flags) {
+ public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
+ int flags) {
boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
boolean securityViolation = includeCode && ai.uid != 0
&& ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
@@ -1394,14 +1428,27 @@
throw new SecurityException(msg);
}
}
- return getPackageInfo(ai, null, securityViolation, includeCode);
+ return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode);
}
- public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) {
- return getPackageInfo(ai, null, false, true);
+ public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
+ CompatibilityInfo compatInfo) {
+ return getPackageInfo(ai, compatInfo, null, false, true);
}
- private final LoadedApk getPackageInfo(ApplicationInfo aInfo,
+ public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) {
+ synchronized (mPackages) {
+ WeakReference<LoadedApk> ref;
+ if (includeCode) {
+ ref = mPackages.get(packageName);
+ } else {
+ ref = mResourcePackages.get(packageName);
+ }
+ return ref != null ? ref.get() : null;
+ }
+ }
+
+ private final LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
synchronized (mPackages) {
WeakReference<LoadedApk> ref;
@@ -1419,7 +1466,7 @@
? mBoundApplication.processName : null)
+ ")");
packageInfo =
- new LoadedApk(this, aInfo, this, baseLoader,
+ new LoadedApk(this, aInfo, compatInfo, this, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
if (includeCode) {
@@ -1476,7 +1523,8 @@
if (mSystemContext == null) {
ContextImpl context =
ContextImpl.createSystemContext(this);
- LoadedApk info = new LoadedApk(this, "android", context, null);
+ LoadedApk info = new LoadedApk(this, "android", context, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);
context.init(info, null, this);
context.getResources().updateConfiguration(
getConfiguration(), getDisplayMetricsLocked(false));
@@ -1491,7 +1539,8 @@
public void installSystemApplicationInfo(ApplicationInfo info) {
synchronized (this) {
ContextImpl context = getSystemContext();
- context.init(new LoadedApk(this, "android", context, info), null, this);
+ context.init(new LoadedApk(this, "android", context, info,
+ new CompatibilityInfo(info, 0, false)), null, this);
}
}
@@ -1641,7 +1690,7 @@
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
- r.packageInfo = getPackageInfo(aInfo.applicationInfo,
+ r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
@@ -1865,7 +1914,7 @@
String component = data.intent.getComponent().getClassName();
LoadedApk packageInfo = getPackageInfoNoCheck(
- data.info.applicationInfo);
+ data.info.applicationInfo, data.compatInfo);
IActivityManager mgr = ActivityManagerNative.getDefault();
@@ -1926,7 +1975,7 @@
unscheduleGcIdler();
// instantiate the BackupAgent class named in the manifest
- LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
+ LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
String packageName = packageInfo.mPackageName;
if (mBackupAgents.get(packageName) != null) {
Slog.d(TAG, "BackupAgent " + " for " + packageName
@@ -1988,7 +2037,7 @@
private final void handleDestroyBackupAgent(CreateBackupAgentData data) {
if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data);
- LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
+ LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
String packageName = packageInfo.mPackageName;
BackupAgent agent = mBackupAgents.get(packageName);
if (agent != null) {
@@ -2010,7 +2059,7 @@
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(
- data.info.applicationInfo);
+ data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
@@ -2727,6 +2776,18 @@
}
}
+ private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) {
+ LoadedApk apk = peekPackageInfo(data.pkg, false);
+ if (apk != null) {
+ apk.mCompatibilityInfo = data.info;
+ }
+ apk = peekPackageInfo(data.pkg, true);
+ if (apk != null) {
+ apk.mCompatibilityInfo = data.info;
+ }
+ handleConfigurationChanged(mConfiguration, data.info);
+ }
+
private final void deliverResults(ActivityClientRecord r, List<ResultInfo> results) {
final int N = results.size();
for (int i=0; i<N; i++) {
@@ -3064,7 +3125,7 @@
// If there was a pending configuration change, execute it first.
if (changedConfig != null) {
- handleConfigurationChanged(changedConfig);
+ handleConfigurationChanged(changedConfig, null);
}
ActivityClientRecord r = mActivities.get(tmp.token);
@@ -3234,11 +3295,12 @@
}
}
- final boolean applyConfigurationToResourcesLocked(Configuration config) {
+ final boolean applyConfigurationToResourcesLocked(Configuration config,
+ CompatibilityInfo compat) {
if (mResConfiguration == null) {
mResConfiguration = new Configuration();
}
- if (!mResConfiguration.isOtherSeqNewer(config)) {
+ if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ mResConfiguration.seq + ", newSeq=" + config.seq);
return false;
@@ -3251,7 +3313,7 @@
Locale.setDefault(config.locale);
}
- Resources.updateSystemConfiguration(config, dm);
+ Resources.updateSystemConfiguration(config, dm, compat);
ApplicationPackageManager.configurationChanged();
//Slog.i(TAG, "Configuration changed in " + currentPackageName());
@@ -3266,7 +3328,7 @@
if (r != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + config);
- r.updateConfiguration(config, dm);
+ r.updateConfiguration(config, dm, compat);
//Slog.i(TAG, "Updated app resources " + v.getKey()
// + " " + r + ": " + r.getConfiguration());
} else {
@@ -3278,7 +3340,7 @@
return changes != 0;
}
- final void handleConfigurationChanged(Configuration config) {
+ final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
ArrayList<ComponentCallbacks> callbacks = null;
@@ -3297,15 +3359,21 @@
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ config);
- applyConfigurationToResourcesLocked(config);
+ applyConfigurationToResourcesLocked(config, compat);
if (mConfiguration == null) {
mConfiguration = new Configuration();
}
- if (!mConfiguration.isOtherSeqNewer(config)) {
+ if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
return;
}
mConfiguration.updateFrom(config);
+ if (compat != null) {
+ // Can't do this here, because it causes us to report the
+ // comatible config back to the am as the current config
+ // of the activity, and much unhappiness results.
+ //compat.applyToConfiguration(mConfiguration);
+ }
callbacks = collectComponentCallbacksLocked(false, config);
}
@@ -3445,9 +3513,10 @@
* reflect configuration changes. The configuration object passed
* in AppBindData can be safely assumed to be up to date
*/
- Resources.getSystem().updateConfiguration(mConfiguration, null);
+ Resources.getSystem().updateConfiguration(mConfiguration,
+ Resources.getSystem().getDisplayMetrics(), data.compatInfo);
- data.info = getPackageInfoNoCheck(data.appInfo);
+ data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
/**
* For system applications on userdebug/eng builds, log stack
@@ -3539,7 +3608,7 @@
instrApp.publicSourceDir = ii.publicSourceDir;
instrApp.dataDir = ii.dataDir;
instrApp.nativeLibraryDir = ii.nativeLibraryDir;
- LoadedApk pi = getPackageInfo(instrApp,
+ LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
appContext.getClassLoader(), false, true);
ContextImpl instrContext = new ContextImpl();
instrContext.init(pi, null, this);
@@ -3953,7 +4022,7 @@
// We need to apply this change to the resources
// immediately, because upon returning the view
// hierarchy will be informed about it.
- if (applyConfigurationToResourcesLocked(newConfig)) {
+ if (applyConfigurationToResourcesLocked(newConfig, null)) {
// This actually changed the resources! Tell
// everyone about it.
if (mPendingConfiguration == null ||
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index aa26b04..e1d76a4 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -23,6 +23,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
+import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
@@ -131,12 +132,13 @@
IBinder b = data.readStrongBinder();
int ident = data.readInt();
ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
+ CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
Bundle state = data.readBundle();
List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
boolean notResumed = data.readInt() != 0;
boolean isForward = data.readInt() != 0;
- scheduleLaunchActivity(intent, b, ident, info, state, ri, pi,
+ scheduleLaunchActivity(intent, b, ident, info, compatInfo, state, ri, pi,
notResumed, isForward);
return true;
}
@@ -181,11 +183,12 @@
data.enforceInterface(IApplicationThread.descriptor);
Intent intent = Intent.CREATOR.createFromParcel(data);
ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
+ CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
int resultCode = data.readInt();
String resultData = data.readString();
Bundle resultExtras = data.readBundle();
boolean sync = data.readInt() != 0;
- scheduleReceiver(intent, info, resultCode, resultData,
+ scheduleReceiver(intent, info, compatInfo, resultCode, resultData,
resultExtras, sync);
return true;
}
@@ -194,7 +197,8 @@
data.enforceInterface(IApplicationThread.descriptor);
IBinder token = data.readStrongBinder();
ServiceInfo info = ServiceInfo.CREATOR.createFromParcel(data);
- scheduleCreateService(token, info);
+ CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
+ scheduleCreateService(token, info, compatInfo);
return true;
}
@@ -256,12 +260,13 @@
int testMode = data.readInt();
boolean restrictedBackupMode = (data.readInt() != 0);
Configuration config = Configuration.CREATOR.createFromParcel(data);
+ CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
HashMap<String, IBinder> services = data.readHashMap(null);
Bundle coreSettings = data.readBundle();
bindApplication(packageName, info,
providers, testName, profileName,
testArgs, testWatcher, testMode, restrictedBackupMode,
- config, services, coreSettings);
+ config, compatInfo, services, coreSettings);
return true;
}
@@ -389,8 +394,9 @@
{
data.enforceInterface(IApplicationThread.descriptor);
ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data);
+ CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
int backupMode = data.readInt();
- scheduleCreateBackupAgent(appInfo, backupMode);
+ scheduleCreateBackupAgent(appInfo, compatInfo, backupMode);
return true;
}
@@ -398,7 +404,8 @@
{
data.enforceInterface(IApplicationThread.descriptor);
ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data);
- scheduleDestroyBackupAgent(appInfo);
+ CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
+ scheduleDestroyBackupAgent(appInfo, compatInfo);
return true;
}
@@ -456,12 +463,20 @@
return true;
}
- case SET_CORE_SETTINGS: {
+ case SET_CORE_SETTINGS_TRANSACTION: {
data.enforceInterface(IApplicationThread.descriptor);
Bundle settings = data.readBundle();
setCoreSettings(settings);
return true;
}
+
+ case UPDATE_PACKAGE_COMPATIBILITY_INFO_TRANSACTION: {
+ data.enforceInterface(IApplicationThread.descriptor);
+ String pkg = data.readString();
+ CompatibilityInfo compat = CompatibilityInfo.CREATOR.createFromParcel(data);
+ updatePackageCompatibilityInfo(pkg, compat);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -554,7 +569,8 @@
}
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,
+ ActivityInfo info, CompatibilityInfo compatInfo, Bundle state,
+ List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -563,6 +579,7 @@
data.writeStrongBinder(token);
data.writeInt(ident);
info.writeToParcel(data, 0);
+ compatInfo.writeToParcel(data, 0);
data.writeBundle(state);
data.writeTypedList(pendingResults);
data.writeTypedList(pendingNewIntents);
@@ -619,12 +636,13 @@
}
public final void scheduleReceiver(Intent intent, ActivityInfo info,
- int resultCode, String resultData,
+ CompatibilityInfo compatInfo, int resultCode, String resultData,
Bundle map, boolean sync) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
intent.writeToParcel(data, 0);
info.writeToParcel(data, 0);
+ compatInfo.writeToParcel(data, 0);
data.writeInt(resultCode);
data.writeString(resultData);
data.writeBundle(map);
@@ -634,32 +652,36 @@
data.recycle();
}
- public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode)
- throws RemoteException {
+ public final void scheduleCreateBackupAgent(ApplicationInfo app,
+ CompatibilityInfo compatInfo, int backupMode) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
app.writeToParcel(data, 0);
+ compatInfo.writeToParcel(data, 0);
data.writeInt(backupMode);
mRemote.transact(SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
- public final void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException {
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app,
+ CompatibilityInfo compatInfo) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
app.writeToParcel(data, 0);
+ compatInfo.writeToParcel(data, 0);
mRemote.transact(SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
- public final void scheduleCreateService(IBinder token, ServiceInfo info)
- throws RemoteException {
+ public final void scheduleCreateService(IBinder token, ServiceInfo info,
+ CompatibilityInfo compatInfo) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
info.writeToParcel(data, 0);
+ compatInfo.writeToParcel(data, 0);
mRemote.transact(SCHEDULE_CREATE_SERVICE_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
@@ -719,7 +741,7 @@
public final void bindApplication(String packageName, ApplicationInfo info,
List<ProviderInfo> providers, ComponentName testName,
String profileName, Bundle testArgs, IInstrumentationWatcher testWatcher, int debugMode,
- boolean restrictedBackupMode, Configuration config,
+ boolean restrictedBackupMode, Configuration config, CompatibilityInfo compatInfo,
Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
@@ -738,6 +760,7 @@
data.writeInt(debugMode);
data.writeInt(restrictedBackupMode ? 1 : 0);
config.writeToParcel(data, 0);
+ compatInfo.writeToParcel(data, 0);
data.writeMap(services);
data.writeBundle(coreSettings);
mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
@@ -952,6 +975,16 @@
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeBundle(coreSettings);
- mRemote.transact(SET_CORE_SETTINGS, data, null, IBinder.FLAG_ONEWAY);
+ mRemote.transact(SET_CORE_SETTINGS_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+ }
+
+ public void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeString(pkg);
+ info.writeToParcel(data, 0);
+ mRemote.transact(UPDATE_PACKAGE_COMPATIBILITY_INFO_TRANSACTION, data, null,
+ IBinder.FLAG_ONEWAY);
}
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cc1f81c..36b9d72 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1372,7 +1372,7 @@
}
LoadedApk pi =
- mMainThread.getPackageInfo(packageName, flags);
+ mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags);
if (pi != null) {
ContextImpl c = new ContextImpl();
c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
@@ -1454,7 +1454,7 @@
" compatiblity info:" + container.getDisplayMetrics());
}
mResources = mainThread.getTopLevelResources(
- mPackageInfo.getResDir(), container.getCompatibilityInfo().copy());
+ mPackageInfo.getResDir(), container.getCompatibilityInfo());
}
mMainThread = mainThread;
mContentResolver = new ApplicationContentResolver(this, mainThread);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 61e6fc8..4c2ccf4 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -342,6 +342,9 @@
public int startActivitiesInPackage(int uid,
Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException;
+ public void setPackageScreenCompatMode(String packageName, boolean compatEnabled)
+ throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -557,4 +560,5 @@
int START_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+120;
int START_ACTIVITIES_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121;
int ACTIVITY_SLEPT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+122;
+ int SET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+123;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 55177a9..93a8ff3 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -23,6 +23,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
+import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Debug;
@@ -52,7 +53,8 @@
void scheduleResumeActivity(IBinder token, boolean isForward) throws RemoteException;
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,
+ ActivityInfo info, CompatibilityInfo compatInfo, Bundle state,
+ List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward)
throws RemoteException;
void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
@@ -61,14 +63,17 @@
void scheduleNewIntent(List<Intent> intent, IBinder token) throws RemoteException;
void scheduleDestroyActivity(IBinder token, boolean finished,
int configChanges) throws RemoteException;
- void scheduleReceiver(Intent intent, ActivityInfo info, int resultCode,
- String data, Bundle extras, boolean sync) throws RemoteException;
+ void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
+ int resultCode, String data, Bundle extras, boolean sync) throws RemoteException;
static final int BACKUP_MODE_INCREMENTAL = 0;
static final int BACKUP_MODE_FULL = 1;
static final int BACKUP_MODE_RESTORE = 2;
- void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) throws RemoteException;
- void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException;
- void scheduleCreateService(IBinder token, ServiceInfo info) throws RemoteException;
+ void scheduleCreateBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo,
+ int backupMode) throws RemoteException;
+ void scheduleDestroyBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo)
+ throws RemoteException;
+ void scheduleCreateService(IBinder token, ServiceInfo info,
+ CompatibilityInfo compatInfo) throws RemoteException;
void scheduleBindService(IBinder token,
Intent intent, boolean rebind) throws RemoteException;
void scheduleUnbindService(IBinder token,
@@ -82,7 +87,7 @@
void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
ComponentName testName, String profileName, Bundle testArguments,
IInstrumentationWatcher testWatcher, int debugMode, boolean restrictedBackupMode,
- Configuration config, Map<String, IBinder> services,
+ Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
Bundle coreSettings) throws RemoteException;
void scheduleExit() throws RemoteException;
void scheduleSuicide() throws RemoteException;
@@ -112,6 +117,7 @@
void dumpActivity(FileDescriptor fd, IBinder servicetoken, String prefix, String[] args)
throws RemoteException;
void setCoreSettings(Bundle coreSettings) throws RemoteException;
+ void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) throws RemoteException;
String descriptor = "android.app.IApplicationThread";
@@ -153,5 +159,6 @@
int DUMP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36;
int CLEAR_DNS_CACHE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+37;
int SET_HTTP_PROXY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+38;
- int SET_CORE_SETTINGS = IBinder.FIRST_CALL_TRANSACTION+39;
+ int SET_CORE_SETTINGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+39;
+ int UPDATE_PACKAGE_COMPATIBILITY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+40;
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index c406524..5307696 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -99,6 +99,7 @@
}
public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
+ CompatibilityInfo compatInfo,
ActivityThread mainThread, ClassLoader baseLoader,
boolean securityViolation, boolean includeCode) {
mActivityThread = activityThread;
@@ -114,7 +115,7 @@
mBaseClassLoader = baseLoader;
mSecurityViolation = securityViolation;
mIncludeCode = includeCode;
- mCompatibilityInfo = new CompatibilityInfo(aInfo);
+ mCompatibilityInfo = compatInfo;
if (mAppDir == null) {
if (ActivityThread.mSystemContext == null) {
@@ -122,7 +123,8 @@
ContextImpl.createSystemContext(mainThread);
ActivityThread.mSystemContext.getResources().updateConfiguration(
mainThread.getConfiguration(),
- mainThread.getDisplayMetricsLocked(false));
+ mainThread.getDisplayMetricsLocked(false),
+ compatInfo);
//Slog.i(TAG, "Created system resources "
// + mSystemContext.getResources() + ": "
// + mSystemContext.getResources().getConfiguration());
@@ -133,7 +135,7 @@
}
public LoadedApk(ActivityThread activityThread, String name,
- Context systemContext, ApplicationInfo info) {
+ Context systemContext, ApplicationInfo info, CompatibilityInfo compatInfo) {
mActivityThread = activityThread;
mApplicationInfo = info != null ? info : new ApplicationInfo();
mApplicationInfo.packageName = name;
@@ -149,7 +151,7 @@
mIncludeCode = true;
mClassLoader = systemContext.getClassLoader();
mResources = systemContext.getResources();
- mCompatibilityInfo = new CompatibilityInfo(mApplicationInfo);
+ mCompatibilityInfo = compatInfo;
}
public String getPackageName() {
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index e403ac2..ab9bfce 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -21,9 +21,9 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
@@ -34,32 +34,27 @@
*
* {@hide}
*/
-public class CompatibilityInfo {
- private static final boolean DBG = false;
- private static final String TAG = "CompatibilityInfo";
-
+public class CompatibilityInfo implements Parcelable {
/** default compatibility info object for compatible applications */
public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() {
- @Override
- public void setExpandable(boolean expandable) {
- throw new UnsupportedOperationException("trying to change default compatibility info");
- }
};
/**
- * The default width of the screen in portrait mode.
+ * This is the number of pixels we would like to have along the
+ * short axis of an app that needs to run on a normal size screen.
*/
- public static final int DEFAULT_PORTRAIT_WIDTH = 320;
+ public static final int DEFAULT_NORMAL_SHORT_DIMENSION = 320;
/**
- * The default height of the screen in portrait mode.
- */
- public static final int DEFAULT_PORTRAIT_HEIGHT = 480;
+ * This is the maximum aspect ratio we will allow while keeping
+ * applications in a compatible screen size.
+ */
+ public static final float MAXIMUM_ASPECT_RATIO = (854f/480f);
/**
* A compatibility flags
*/
- private int mCompatibilityFlags;
+ private final int mCompatibilityFlags;
/**
* A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
@@ -68,54 +63,27 @@
private static final int SCALING_REQUIRED = 1;
/**
- * A flag mask to indicates that the application can expand over the original size.
- * The flag is set to true if
- * 1) Application declares its expandable in manifest file using <supports-screens> or
- * 2) Configuration.SCREENLAYOUT_COMPAT_NEEDED is not set
- * {@see compatibilityFlag}
+ * Has the application said that its UI is expandable? Based on the
+ * <supports-screen> android:expandible in the manifest.
*/
private static final int EXPANDABLE = 2;
/**
- * A flag mask to tell if the application is configured to be expandable. This differs
- * from EXPANDABLE in that the application that is not expandable will be
- * marked as expandable if Configuration.SCREENLAYOUT_COMPAT_NEEDED is not set.
- */
- private static final int CONFIGURED_EXPANDABLE = 4;
-
- /**
- * A flag mask to indicates that the application supports large screens.
- * The flag is set to true if
- * 1) Application declares it supports large screens in manifest file using <supports-screens> or
- * 2) The screen size is not large
- * {@see compatibilityFlag}
+ * Has the application said that its UI supports large screens? Based on the
+ * <supports-screen> android:largeScreens in the manifest.
*/
private static final int LARGE_SCREENS = 8;
/**
- * A flag mask to tell if the application supports large screens. This differs
- * from LARGE_SCREENS in that the application that does not support large
- * screens will be marked as supporting them if the current screen is not
- * large.
- */
- private static final int CONFIGURED_LARGE_SCREENS = 16;
-
- /**
- * A flag mask to indicates that the application supports xlarge screens.
- * The flag is set to true if
- * 1) Application declares it supports xlarge screens in manifest file using <supports-screens> or
- * 2) The screen size is not xlarge
- * {@see compatibilityFlag}
+ * Has the application said that its UI supports xlarge screens? Based on the
+ * <supports-screen> android:xlargeScreens in the manifest.
*/
private static final int XLARGE_SCREENS = 32;
/**
- * A flag mask to tell if the application supports xlarge screens. This differs
- * from XLARGE_SCREENS in that the application that does not support xlarge
- * screens will be marked as supporting them if the current screen is not
- * xlarge.
+ * Set if the application needs to run in screen size compatibility mode.
*/
- private static final int CONFIGURED_XLARGE_SCREENS = 64;
+ private static final int NEEDS_SCREEN_COMPAT = 128;
/**
* The effective screen density we have selected for this application.
@@ -132,28 +100,55 @@
*/
public final float applicationInvertedScale;
- /**
- * The flags from ApplicationInfo.
- */
- public final int appFlags;
-
- public CompatibilityInfo(ApplicationInfo appInfo) {
- appFlags = appInfo.flags;
-
+ public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, boolean forceCompat) {
+ int compatFlags = 0;
+
if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
- // Saying you support large screens also implies you support xlarge
- // screens; there is no compatibility mode for a large app on an
- // xlarge screen.
- mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS
- | XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS
- | EXPANDABLE | CONFIGURED_EXPANDABLE;
+ compatFlags |= LARGE_SCREENS;
+ if (!forceCompat) {
+ // If we aren't forcing the app into compatibility mode, then
+ // assume if it supports large screens that we should allow it
+ // to use the full space of an xlarge screen as well.
+ compatFlags |= XLARGE_SCREENS | EXPANDABLE;
+ }
}
if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
- mCompatibilityFlags |= XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS
- | EXPANDABLE | CONFIGURED_EXPANDABLE;
+ compatFlags |= XLARGE_SCREENS | EXPANDABLE;
}
- if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
- mCompatibilityFlags |= EXPANDABLE | CONFIGURED_EXPANDABLE;
+ if (!forceCompat) {
+ // If we are forcing compatibility mode, then ignore an app that
+ // just says it is resizable for screens. We'll only have it fill
+ // the screen if it explicitly says it supports the screen size we
+ // are running in.
+ if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+ compatFlags |= EXPANDABLE;
+ }
+ }
+
+ boolean supportsScreen = false;
+ switch (screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) {
+ case Configuration.SCREENLAYOUT_SIZE_XLARGE:
+ if ((compatFlags&XLARGE_SCREENS) != 0) {
+ supportsScreen = true;
+ }
+ break;
+ case Configuration.SCREENLAYOUT_SIZE_LARGE:
+ if ((compatFlags&LARGE_SCREENS) != 0) {
+ supportsScreen = true;
+ }
+ break;
+ }
+
+ if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) == 0) {
+ if ((compatFlags&EXPANDABLE) != 0) {
+ supportsScreen = true;
+ }
+ }
+
+ if (supportsScreen) {
+ compatFlags &= ~NEEDS_SCREEN_COMPAT;
+ } else {
+ compatFlags |= NEEDS_SCREEN_COMPAT;
}
if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
@@ -165,13 +160,14 @@
applicationScale = DisplayMetrics.DENSITY_DEVICE
/ (float) DisplayMetrics.DENSITY_DEFAULT;
applicationInvertedScale = 1.0f / applicationScale;
- mCompatibilityFlags |= SCALING_REQUIRED;
+ compatFlags |= SCALING_REQUIRED;
}
+
+ mCompatibilityFlags = compatFlags;
}
- private CompatibilityInfo(int appFlags, int compFlags,
+ private CompatibilityInfo(int compFlags,
int dens, float scale, float invertedScale) {
- this.appFlags = appFlags;
mCompatibilityFlags = compFlags;
applicationDensity = dens;
applicationScale = scale;
@@ -179,81 +175,13 @@
}
private CompatibilityInfo() {
- this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
- | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS
- | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS
- | ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS
- | ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS,
- EXPANDABLE | CONFIGURED_EXPANDABLE,
+ this(XLARGE_SCREENS | LARGE_SCREENS | EXPANDABLE,
DisplayMetrics.DENSITY_DEVICE,
1.0f,
1.0f);
}
/**
- * Returns the copy of this instance.
- */
- public CompatibilityInfo copy() {
- CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags,
- applicationDensity, applicationScale, applicationInvertedScale);
- return info;
- }
-
- /**
- * Sets expandable bit in the compatibility flag.
- */
- public void setExpandable(boolean expandable) {
- if (expandable) {
- mCompatibilityFlags |= CompatibilityInfo.EXPANDABLE;
- } else {
- mCompatibilityFlags &= ~CompatibilityInfo.EXPANDABLE;
- }
- }
-
- /**
- * Sets large screen bit in the compatibility flag.
- */
- public void setLargeScreens(boolean expandable) {
- if (expandable) {
- mCompatibilityFlags |= CompatibilityInfo.LARGE_SCREENS;
- } else {
- mCompatibilityFlags &= ~CompatibilityInfo.LARGE_SCREENS;
- }
- }
-
- /**
- * Sets large screen bit in the compatibility flag.
- */
- public void setXLargeScreens(boolean expandable) {
- if (expandable) {
- mCompatibilityFlags |= CompatibilityInfo.XLARGE_SCREENS;
- } else {
- mCompatibilityFlags &= ~CompatibilityInfo.XLARGE_SCREENS;
- }
- }
-
- /**
- * @return true if the application is configured to be expandable.
- */
- public boolean isConfiguredExpandable() {
- return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_EXPANDABLE) != 0;
- }
-
- /**
- * @return true if the application is configured to be expandable.
- */
- public boolean isConfiguredLargeScreens() {
- return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_LARGE_SCREENS) != 0;
- }
-
- /**
- * @return true if the application is configured to be expandable.
- */
- public boolean isConfiguredXLargeScreens() {
- return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_XLARGE_SCREENS) != 0;
- }
-
- /**
* @return true if the scaling is required
*/
public boolean isScalingRequired() {
@@ -261,14 +189,12 @@
}
public boolean supportsScreen() {
- return (mCompatibilityFlags & (EXPANDABLE|LARGE_SCREENS))
- == (EXPANDABLE|LARGE_SCREENS);
+ return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
}
@Override
public String toString() {
- return "CompatibilityInfo{scale=" + applicationScale +
- ", supports screen=" + supportsScreen() + "}";
+ return "CompatibilityInfo{scale=" + applicationScale + "}";
}
/**
@@ -423,24 +349,144 @@
}
}
+ public void applyToDisplayMetrics(DisplayMetrics inoutDm) {
+ if (!supportsScreen()) {
+ // This is a larger screen device and the app is not
+ // compatible with large screens, so diddle it.
+ CompatibilityInfo.updateCompatibleScreenFrame(inoutDm, null, inoutDm);
+ }
+
+ if (isScalingRequired()) {
+ float invertedRatio = applicationInvertedScale;
+ inoutDm.density *= invertedRatio;
+ inoutDm.densityDpi = (int)((inoutDm.density*DisplayMetrics.DENSITY_DEFAULT)+.5f);
+ inoutDm.scaledDensity *= invertedRatio;
+ inoutDm.xdpi *= invertedRatio;
+ inoutDm.ydpi *= invertedRatio;
+ inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
+ inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
+ }
+ }
+
+ public void applyToConfiguration(Configuration inoutConfig) {
+ if (!supportsScreen()) {
+ // This is a larger screen device and the app is not
+ // compatible with large screens, so we are forcing it to
+ // run as if the screen is normal size.
+ inoutConfig.screenLayout =
+ (inoutConfig.screenLayout&~Configuration.SCREENLAYOUT_SIZE_MASK)
+ | Configuration.SCREENLAYOUT_SIZE_NORMAL;
+ }
+ }
+
/**
- * Returns the frame Rect for applications runs under compatibility mode.
+ * Compute the frame Rect for applications runs under compatibility mode.
*
* @param dm the display metrics used to compute the frame size.
* @param orientation the orientation of the screen.
* @param outRect the output parameter which will contain the result.
+ * @return Returns the scaling factor for the window.
*/
- public static void updateCompatibleScreenFrame(DisplayMetrics dm, int orientation,
- Rect outRect) {
- int width = dm.widthPixels;
- int portraitHeight = (int) (DEFAULT_PORTRAIT_HEIGHT * dm.density + 0.5f);
- int portraitWidth = (int) (DEFAULT_PORTRAIT_WIDTH * dm.density + 0.5f);
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- int xOffset = (width - portraitHeight) / 2 ;
- outRect.set(xOffset, 0, xOffset + portraitHeight, portraitWidth);
+ public static float updateCompatibleScreenFrame(DisplayMetrics dm,
+ Rect outRect, DisplayMetrics outDm) {
+ final int width = dm.realWidthPixels;
+ final int height = dm.realHeightPixels;
+ int shortSize, longSize;
+ if (width < height) {
+ shortSize = width;
+ longSize = height;
} else {
- int xOffset = (width - portraitWidth) / 2 ;
- outRect.set(xOffset, 0, xOffset + portraitWidth, portraitHeight);
+ shortSize = height;
+ longSize = width;
}
+ int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f);
+ float aspect = ((float)longSize) / shortSize;
+ if (aspect > MAXIMUM_ASPECT_RATIO) {
+ aspect = MAXIMUM_ASPECT_RATIO;
+ }
+ int newLongSize = (int)(newShortSize * aspect + 0.5f);
+ int newWidth, newHeight;
+ if (width < height) {
+ newWidth = newShortSize;
+ newHeight = newLongSize;
+ } else {
+ newWidth = newLongSize;
+ newHeight = newShortSize;
+ }
+
+ float sw = width/(float)newWidth;
+ float sh = height/(float)newHeight;
+ float scale = sw < sh ? sw : sh;
+ if (scale < 1) {
+ scale = 1;
+ }
+
+ if (outRect != null) {
+ final int left = (int)((width-(newWidth*scale))/2);
+ final int top = (int)((height-(newHeight*scale))/2);
+ outRect.set(left, top, left+newWidth, top+newHeight);
+ }
+
+ if (outDm != null) {
+ outDm.widthPixels = newWidth;
+ outDm.heightPixels = newHeight;
+ }
+
+ return scale;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ try {
+ CompatibilityInfo oc = (CompatibilityInfo)o;
+ if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
+ if (applicationDensity != oc.applicationDensity) return false;
+ if (applicationScale != oc.applicationScale) return false;
+ if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+ return true;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mCompatibilityFlags;
+ result = 31 * result + applicationDensity;
+ result = 31 * result + Float.floatToIntBits(applicationScale);
+ result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+ return result;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mCompatibilityFlags);
+ dest.writeInt(applicationDensity);
+ dest.writeFloat(applicationScale);
+ dest.writeFloat(applicationInvertedScale);
+ }
+
+ public static final Parcelable.Creator<CompatibilityInfo> CREATOR
+ = new Parcelable.Creator<CompatibilityInfo>() {
+ public CompatibilityInfo createFromParcel(Parcel source) {
+ return new CompatibilityInfo(source);
+ }
+
+ public CompatibilityInfo[] newArray(int size) {
+ return new CompatibilityInfo[size];
+ }
+ };
+
+ private CompatibilityInfo(Parcel source) {
+ mCompatibilityFlags = source.readInt();
+ applicationDensity = source.readInt();
+ applicationScale = source.readFloat();
+ applicationInvertedScale = source.readFloat();
}
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 47c2623..908db11 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -703,11 +703,20 @@
}
public int hashCode() {
- return ((int)this.fontScale) + this.mcc + this.mnc
- + (this.locale != null ? this.locale.hashCode() : 0)
- + this.touchscreen
- + this.keyboard + this.keyboardHidden + this.hardKeyboardHidden
- + this.navigation + this.navigationHidden
- + this.orientation + this.screenLayout + this.uiMode;
+ int result = 17;
+ result = 31 * result + Float.floatToIntBits(fontScale);
+ result = 31 * result + mcc;
+ result = 31 * result + mnc;
+ result = 31 * result + (locale != null ? locale.hashCode() : 0);
+ result = 31 * result + touchscreen;
+ result = 31 * result + keyboard;
+ result = 31 * result + keyboardHidden;
+ result = 31 * result + hardKeyboardHidden;
+ result = 31 * result + navigation;
+ result = 31 * result + navigationHidden;
+ result = 31 * result + orientation;
+ result = 31 * result + screenLayout;
+ result = 31 * result + uiMode;
+ return result;
}
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 81eb09c..00b49e8 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -91,6 +91,7 @@
private static boolean mPreloaded;
/*package*/ final TypedValue mTmpValue = new TypedValue();
+ /*package*/ final Configuration mTmpConfig = new Configuration();
// These are protected by the mTmpValue lock.
private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
@@ -1400,18 +1401,30 @@
*/
public void updateConfiguration(Configuration config,
DisplayMetrics metrics) {
+ updateConfiguration(config, metrics, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void updateConfiguration(Configuration config,
+ DisplayMetrics metrics, CompatibilityInfo compat) {
synchronized (mTmpValue) {
+ if (compat != null) {
+ mCompatibilityInfo = compat;
+ }
int configChanges = 0xfffffff;
if (config != null) {
- configChanges = mConfiguration.updateFrom(config);
+ mTmpConfig.setTo(config);
+ mCompatibilityInfo.applyToConfiguration(mTmpConfig);
+ configChanges = mConfiguration.updateFrom(mTmpConfig);
}
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
}
if (metrics != null) {
mMetrics.setTo(metrics);
- mMetrics.updateMetrics(mCompatibilityInfo,
- mConfiguration.orientation, mConfiguration.screenLayout);
+ mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
}
mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
@@ -1500,15 +1513,23 @@
*
* @hide
*/
- public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics) {
+ public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics,
+ CompatibilityInfo compat) {
if (mSystem != null) {
- mSystem.updateConfiguration(config, metrics);
+ mSystem.updateConfiguration(config, metrics, compat);
//Log.i(TAG, "Updated system resources " + mSystem
// + ": " + mSystem.getConfiguration());
}
}
/**
+ * @hide
+ */
+ public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics) {
+ updateSystemConfiguration(config, metrics, null);
+ }
+
+ /**
* Return the current display metrics that are in effect for this resource
* object. The returned object should be treated as read-only.
*
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 4c83515..27da3c3 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -637,6 +637,22 @@
}
return _result;
}
+
+ public String[] getVolumeList() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ String[] _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readStringArray();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
}
private static final String DESCRIPTOR = "IMountService";
@@ -699,6 +715,8 @@
static final int TRANSACTION_changeEncryptionPassword = IBinder.FIRST_CALL_TRANSACTION + 28;
+ static final int TRANSACTION_getVolumeList = IBinder.FIRST_CALL_TRANSACTION + 29;
+
/**
* Cast an IBinder object into an IMountService interface, generating a
* proxy if needed.
@@ -1004,6 +1022,13 @@
reply.writeInt(result);
return true;
}
+ case TRANSACTION_getVolumeList: {
+ data.enforceInterface(DESCRIPTOR);
+ String[] result = getVolumeList();
+ reply.writeNoException();
+ reply.writeStringArray(result);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -1179,4 +1204,8 @@
*/
public int changeEncryptionPassword(String password) throws RemoteException;
+ /**
+ * Returns list of all mountable volumes.
+ */
+ public String[] getVolumeList() throws RemoteException;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 73ac79f..234057b 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -527,4 +527,30 @@
return null;
}
+
+ /**
+ * Gets the state of a volume via its mountpoint.
+ * @hide
+ */
+ public String getVolumeState(String mountPoint) {
+ try {
+ return mMountService.getVolumeState(mountPoint);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get volume state", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns list of all mountable volumes.
+ * @hide
+ */
+ public String[] getVolumeList() {
+ try {
+ return mMountService.getVolumeList();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get volume list", e);
+ return null;
+ }
+ }
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index b59421e..c9b2f97 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -344,6 +344,13 @@
*/
public interface FileColumns extends MediaColumns {
/**
+ * The MTP storage ID of the file
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String STORAGE_ID = "storage_id";
+
+ /**
* The MTP format code of the file
* <P>Type: INTEGER</P>
* @hide
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 63baf14..8018ff9 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -16,9 +16,7 @@
package android.util;
-import android.content.res.CompatibilityInfo;
-import android.content.res.Configuration;
-import android.os.*;
+import android.os.SystemProperties;
/**
@@ -107,6 +105,11 @@
*/
public float ydpi;
+ /** @hide */
+ public int realWidthPixels;
+ /** @hide */
+ public int realHeightPixels;
+
public DisplayMetrics() {
}
@@ -118,6 +121,8 @@
scaledDensity = o.scaledDensity;
xdpi = o.xdpi;
ydpi = o.ydpi;
+ realWidthPixels = o.realWidthPixels;
+ realHeightPixels = o.realHeightPixels;
}
public void setToDefaults() {
@@ -128,101 +133,8 @@
scaledDensity = density;
xdpi = DENSITY_DEVICE;
ydpi = DENSITY_DEVICE;
- }
-
- /**
- * Update the display metrics based on the compatibility info and orientation
- * NOTE: DO NOT EXPOSE THIS API! It is introducing a circular dependency
- * with the higher-level android.res package.
- * {@hide}
- */
- public void updateMetrics(CompatibilityInfo compatibilityInfo, int orientation,
- int screenLayout) {
- boolean expandable = compatibilityInfo.isConfiguredExpandable();
- boolean largeScreens = compatibilityInfo.isConfiguredLargeScreens();
- boolean xlargeScreens = compatibilityInfo.isConfiguredXLargeScreens();
-
- // Note: this assume that configuration is updated before calling
- // updateMetrics method.
- if (!expandable) {
- if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) == 0) {
- expandable = true;
- // the current screen size is compatible with non-resizing apps.
- compatibilityInfo.setExpandable(true);
- } else {
- compatibilityInfo.setExpandable(false);
- }
- }
- if (!largeScreens) {
- if ((screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK)
- != Configuration.SCREENLAYOUT_SIZE_LARGE) {
- largeScreens = true;
- // the current screen size is not large.
- compatibilityInfo.setLargeScreens(true);
- } else {
- compatibilityInfo.setLargeScreens(false);
- }
- }
- if (!xlargeScreens) {
- if ((screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK)
- != Configuration.SCREENLAYOUT_SIZE_XLARGE) {
- xlargeScreens = true;
- // the current screen size is not large.
- compatibilityInfo.setXLargeScreens(true);
- } else {
- compatibilityInfo.setXLargeScreens(false);
- }
- }
-
- if (!expandable || (!largeScreens && !xlargeScreens)) {
- // This is a larger screen device and the app is not
- // compatible with large screens, so diddle it.
-
- // Figure out the compatibility width and height of the screen.
- int defaultWidth;
- int defaultHeight;
- switch (orientation) {
- case Configuration.ORIENTATION_LANDSCAPE: {
- defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density +
- 0.5f);
- defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density +
- 0.5f);
- break;
- }
- case Configuration.ORIENTATION_PORTRAIT:
- case Configuration.ORIENTATION_SQUARE:
- default: {
- defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density +
- 0.5f);
- defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density +
- 0.5f);
- break;
- }
- case Configuration.ORIENTATION_UNDEFINED: {
- // don't change
- return;
- }
- }
-
- if (defaultWidth < widthPixels) {
- // content/window's x offset in original pixels
- widthPixels = defaultWidth;
- }
- if (defaultHeight < heightPixels) {
- heightPixels = defaultHeight;
- }
- }
-
- if (compatibilityInfo.isScalingRequired()) {
- float invertedRatio = compatibilityInfo.applicationInvertedScale;
- density *= invertedRatio;
- densityDpi = (int)((density*DisplayMetrics.DENSITY_DEFAULT)+.5f);
- scaledDensity *= invertedRatio;
- xdpi *= invertedRatio;
- ydpi *= invertedRatio;
- widthPixels = (int) (widthPixels * invertedRatio + 0.5f);
- heightPixels = (int) (heightPixels * invertedRatio + 0.5f);
- }
+ realWidthPixels = 0;
+ realHeightPixels = 0;
}
@Override
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 126f409..89767f2 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -139,6 +139,9 @@
outMetrics.scaledDensity= outMetrics.density;
outMetrics.xdpi = mDpiX;
outMetrics.ydpi = mDpiY;
+
+ outMetrics.realWidthPixels = outMetrics.widthPixels;
+ outMetrics.realHeightPixels = outMetrics.heightPixels;
}
/*
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 334c68e..2058991 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -458,6 +458,18 @@
public int getMaxWallpaperLayer();
/**
+ * Return the display width available after excluding the window
+ * decor.
+ */
+ public int getNonDecorDisplayWidth(int fullWidth);
+
+ /**
+ * Return the display height available after excluding the screen
+ * decor.
+ */
+ public int getNonDecorDisplayHeight(int fullHeight);
+
+ /**
* Return whether the given window should forcibly hide everything
* behind it. Typically returns true for the keyguard.
*/
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index 060c0bb..d1b8cfc 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -184,7 +184,9 @@
// we need to pause the old one and re-create a new media player
// inside the HTML5VideoView.
if (mHTML5VideoView != null) {
- mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
+ if (!backFromFullScreenMode) {
+ mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
+ }
// release the media player to avoid finalize error
mHTML5VideoView.release();
}
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 667ba75..4e363bf 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -459,7 +459,9 @@
}
// reply comes back in the form "powermode = XX" where XX is the
// number we're interested in.
- sscanf(reply, "%*s = %u", &power);
+ if (sscanf(reply, "%*s = %u", &power) != 1) {
+ return (jint)-1;
+ }
return (jint)power;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c684e7e..2f431c4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -544,6 +544,14 @@
android:description="@string/permdesc_sdcardWrite"
android:protectionLevel="dangerous" />
+ <!-- Allows an application to write to internal media storage
+ @hide -->
+ <permission android:name="android.permission.WRITE_MEDIA_STORAGE"
+ android:permissionGroup="android.permission-group.STORAGE"
+ android:label="@string/permlab_mediaStorageWrite"
+ android:description="@string/permdesc_mediaStorageWrite"
+ android:protectionLevel="signatureOrSystem" />
+
<!-- ============================================ -->
<!-- Permissions for low-level system interaction -->
<!-- ============================================ -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e46eecc..27c7a4d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -109,6 +109,23 @@
removable. -->
<bool name="config_externalStorageRemovable" product="default">true</bool>
+ <!-- List of mount points for external storage devices.
+ The first item on the list should be the primary external storage and should match the
+ value returned by Environment.getExternalStorageDirectory (/mnt/sdcard).
+ MTP storage IDs will be generated based on the position of the mountpoint in this list:
+ 0x00010001 - ID for primary external storage (/mnt/sdcard)
+ 0x00020001 - ID for first secondary external storage
+ 0x00030001 - ID for second secondary external storage
+ etc. -->
+ <string-array translatable="false" name="config_externalStoragePaths">
+ <item>"/mnt/sdcard"</item>
+ </string-array>
+
+ <!-- User visible descriptions of the volumes in the config_externalStoragePaths array. -->
+ <string-array translatable="true" name="config_externalStorageDescriptions">
+ <item>"SD card"</item>
+ </string-array>
+
<!-- Number of megabytes of space to leave unallocated by MTP.
MTP will subtract this value from the free space it reports back
to the host via GetStorageInfo, and will not allow new files to
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index df80546..1870a4a 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -58,6 +58,10 @@
<group gid="sdcard_rw" />
</permission>
+ <permission name="android.permission.WRITE_MEDIA_STORAGE" >
+ <group gid="media_rw" />
+ </permission>
+
<permission name="android.permission.ACCESS_MTP" >
<group gid="mtp" />
</permission>
diff --git a/include/media/mediascanner.h b/include/media/mediascanner.h
index df5be32..765c039 100644
--- a/include/media/mediascanner.h
+++ b/include/media/mediascanner.h
@@ -55,7 +55,7 @@
status_t doProcessDirectory(
char *path, int pathRemaining, MediaScannerClient &client,
- ExceptionCheck exceptionCheck, void *exceptionEnv);
+ bool noMedia, ExceptionCheck exceptionCheck, void *exceptionEnv);
MediaScanner(const MediaScanner &);
MediaScanner &operator=(const MediaScanner &);
@@ -72,10 +72,9 @@
void endFile();
virtual bool scanFile(const char* path, long long lastModified,
- long long fileSize, bool isDirectory) = 0;
+ long long fileSize, bool isDirectory, bool noMedia) = 0;
virtual bool handleStringTag(const char* name, const char* value) = 0;
virtual bool setMimeType(const char* mimeType) = 0;
- virtual bool addNoMediaFolder(const char* path) = 0;
protected:
void convertValues(uint32_t encoding);
diff --git a/include/ui/Input.h b/include/ui/Input.h
index d9d77c4..bc5b718 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -203,6 +203,8 @@
status_t setAxisValue(int32_t axis, float value);
float* editAxisValue(int32_t axis);
+ void scale(float scale);
+
#ifdef HAVE_ANDROID_OS
status_t readFromParcel(Parcel* parcel);
status_t writeToParcel(Parcel* parcel) const;
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index e2e698e..19d590a 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -298,6 +298,24 @@
return &values[index];
}
+static inline void scaleAxisValue(PointerCoords& c, int axis, float scaleFactor) {
+ float* value = c.editAxisValue(axis);
+ if (value) {
+ *value *= scaleFactor;
+ }
+}
+
+void PointerCoords::scale(float scaleFactor) {
+ // No need to scale pressure or size since they are normalized.
+ // No need to scale orientation since it is meaningless to do so.
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_X, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_Y, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MAJOR, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MINOR, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MAJOR, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MINOR, scaleFactor);
+}
+
#ifdef HAVE_ANDROID_OS
status_t PointerCoords::readFromParcel(Parcel* parcel) {
bits = parcel->readInt64();
@@ -411,11 +429,9 @@
float value = getRawPointerCoords(pointerIndex)->getAxisValue(axis);
switch (axis) {
case AMOTION_EVENT_AXIS_X:
- value += mXOffset;
- break;
+ return value + mXOffset;
case AMOTION_EVENT_AXIS_Y:
- value += mYOffset;
- break;
+ return value + mYOffset;
}
return value;
}
@@ -435,11 +451,9 @@
float value = getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
switch (axis) {
case AMOTION_EVENT_AXIS_X:
- value += mXOffset;
- break;
+ return value + mXOffset;
case AMOTION_EVENT_AXIS_Y:
- value += mYOffset;
- break;
+ return value + mYOffset;
}
return value;
}
@@ -449,13 +463,6 @@
mYOffset += yOffset;
}
-static inline void scaleAxisValue(PointerCoords& c, int axis, float scaleFactor) {
- float* value = c.editAxisValue(axis);
- if (value) {
- *value *= scaleFactor;
- }
-}
-
void MotionEvent::scale(float scaleFactor) {
mXOffset *= scaleFactor;
mYOffset *= scaleFactor;
@@ -464,15 +471,7 @@
size_t numSamples = mSamplePointerCoords.size();
for (size_t i = 0; i < numSamples; i++) {
- PointerCoords& c = mSamplePointerCoords.editItemAt(i);
- // No need to scale pressure or size since they are normalized.
- // No need to scale orientation since it is meaningless to do so.
- scaleAxisValue(c, AMOTION_EVENT_AXIS_X, scaleFactor);
- scaleAxisValue(c, AMOTION_EVENT_AXIS_Y, scaleFactor);
- scaleAxisValue(c, AMOTION_EVENT_AXIS_TOUCH_MAJOR, scaleFactor);
- scaleAxisValue(c, AMOTION_EVENT_AXIS_TOUCH_MINOR, scaleFactor);
- scaleAxisValue(c, AMOTION_EVENT_AXIS_TOOL_MAJOR, scaleFactor);
- scaleAxisValue(c, AMOTION_EVENT_AXIS_TOOL_MINOR, scaleFactor);
+ mSamplePointerCoords.editItemAt(i).scale(scaleFactor);
}
}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 74d65d1..d1eb388 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -347,7 +347,6 @@
private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
private static class FileCacheEntry {
- Uri mTableUri;
long mRowId;
String mPath;
long mLastModified;
@@ -355,8 +354,7 @@
boolean mSeenInFileSystem;
boolean mLastModifiedChanged;
- FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified, int format) {
- mTableUri = tableUri;
+ FileCacheEntry(long rowId, String path, long lastModified, int format) {
mRowId = rowId;
mPath = path;
mLastModified = lastModified;
@@ -367,7 +365,7 @@
@Override
public String toString() {
- return mPath + " mTableUri: " + mTableUri + " mRowId: " + mRowId;
+ return mPath + " mRowId: " + mRowId;
}
}
@@ -425,41 +423,19 @@
private long mFileSize;
private String mWriter;
private int mCompilation;
+ private boolean mNoMedia; // flag to suppress file from appearing in media tables
public FileCacheEntry beginFile(String path, String mimeType, long lastModified,
- long fileSize, boolean isDirectory) {
+ long fileSize, boolean isDirectory, boolean noMedia) {
mMimeType = mimeType;
mFileType = 0;
mFileSize = fileSize;
if (!isDirectory) {
- // special case certain file names
- // I use regionMatches() instead of substring() below
- // to avoid memory allocation
- int lastSlash = path.lastIndexOf('/');
- if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
- // ignore those ._* files created by MacOS
- if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
- return null;
- }
-
- // ignore album art files created by Windows Media Player:
- // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg
- // and AlbumArt_{...}_Small.jpg
- if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
- if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
- path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
- return null;
- }
- int length = path.length() - lastSlash - 1;
- if ((length == 17 && path.regionMatches(
- true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
- (length == 10
- && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
- return null;
- }
- }
+ if (!noMedia && isNoMediaFile(path)) {
+ noMedia = true;
}
+ mNoMedia = noMedia;
// try mimeType first, if it is specified
if (mimeType != null) {
@@ -491,23 +467,10 @@
long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
boolean wasModified = delta > 1 || delta < -1;
if (entry == null || wasModified) {
- Uri tableUri;
- if (isDirectory) {
- tableUri = mFilesUri;
- } else if (MediaFile.isVideoFileType(mFileType)) {
- tableUri = mVideoUri;
- } else if (MediaFile.isImageFileType(mFileType)) {
- tableUri = mImagesUri;
- } else if (MediaFile.isAudioFileType(mFileType)) {
- tableUri = mAudioUri;
- } else {
- tableUri = mFilesUri;
- }
if (wasModified) {
entry.mLastModified = lastModified;
- entry.mTableUri = tableUri;
} else {
- entry = new FileCacheEntry(tableUri, 0, path, lastModified,
+ entry = new FileCacheEntry(0, path, lastModified,
(isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0));
mFileCache.put(key, entry);
}
@@ -539,36 +502,41 @@
return entry;
}
- public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory) {
+ public void scanFile(String path, long lastModified, long fileSize,
+ boolean isDirectory, boolean noMedia) {
// This is the callback funtion from native codes.
// Log.v(TAG, "scanFile: "+path);
- doScanFile(path, null, lastModified, fileSize, isDirectory, false);
+ doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
}
public Uri doScanFile(String path, String mimeType, long lastModified,
- long fileSize, boolean isDirectory, boolean scanAlways) {
+ long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) {
Uri result = null;
// long t1 = System.currentTimeMillis();
try {
FileCacheEntry entry = beginFile(path, mimeType, lastModified,
- fileSize, isDirectory);
+ fileSize, isDirectory, noMedia);
// rescan for metadata if file was modified since last scan
if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
- String lowpath = path.toLowerCase();
- boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);
- boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
- boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0);
- boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0);
- boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) ||
- (!ringtones && !notifications && !alarms && !podcasts);
+ if (noMedia) {
+ result = endFile(entry, false, false, false, false, false);
+ } else {
+ String lowpath = path.toLowerCase();
+ boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);
+ boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
+ boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0);
+ boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0);
+ boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) ||
+ (!ringtones && !notifications && !alarms && !podcasts);
- // we only extract metadata for audio and video files
- if (MediaFile.isAudioFileType(mFileType)
- || MediaFile.isVideoFileType(mFileType)) {
- processFile(path, mimeType, this);
+ // we only extract metadata for audio and video files
+ if (MediaFile.isAudioFileType(mFileType)
+ || MediaFile.isVideoFileType(mFileType)) {
+ processFile(path, mimeType, this);
+ }
+
+ result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
}
-
- result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
@@ -705,27 +673,31 @@
map.put(MediaStore.MediaColumns.SIZE, mFileSize);
map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
- if (MediaFile.isVideoFileType(mFileType)) {
- map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaStore.UNKNOWN_STRING));
- map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaStore.UNKNOWN_STRING));
- map.put(Video.Media.DURATION, mDuration);
- // FIXME - add RESOLUTION
- } else if (MediaFile.isImageFileType(mFileType)) {
- // FIXME - add DESCRIPTION
- } else if (MediaFile.isAudioFileType(mFileType)) {
- map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
- mArtist : MediaStore.UNKNOWN_STRING);
- map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
- mAlbumArtist.length() > 0) ? mAlbumArtist : null);
- map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
- mAlbum : MediaStore.UNKNOWN_STRING);
- map.put(Audio.Media.COMPOSER, mComposer);
- if (mYear != 0) {
- map.put(Audio.Media.YEAR, mYear);
+ if (!mNoMedia) {
+ if (MediaFile.isVideoFileType(mFileType)) {
+ map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0
+ ? mArtist : MediaStore.UNKNOWN_STRING));
+ map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0
+ ? mAlbum : MediaStore.UNKNOWN_STRING));
+ map.put(Video.Media.DURATION, mDuration);
+ // FIXME - add RESOLUTION
+ } else if (MediaFile.isImageFileType(mFileType)) {
+ // FIXME - add DESCRIPTION
+ } else if (MediaFile.isAudioFileType(mFileType)) {
+ map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
+ mArtist : MediaStore.UNKNOWN_STRING);
+ map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
+ mAlbumArtist.length() > 0) ? mAlbumArtist : null);
+ map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
+ mAlbum : MediaStore.UNKNOWN_STRING);
+ map.put(Audio.Media.COMPOSER, mComposer);
+ if (mYear != 0) {
+ map.put(Audio.Media.YEAR, mYear);
+ }
+ map.put(Audio.Media.TRACK, mTrack);
+ map.put(Audio.Media.DURATION, mDuration);
+ map.put(Audio.Media.COMPILATION, mCompilation);
}
- map.put(Audio.Media.TRACK, mTrack);
- map.put(Audio.Media.DURATION, mDuration);
- map.put(Audio.Media.COMPILATION, mCompilation);
}
return map;
}
@@ -735,7 +707,7 @@
throws RemoteException {
// update database
- // use album artist if artist is missing
+ // use album artist if artist is missing
if (mArtist == null || mArtist.length() == 0) {
mArtist = mAlbumArtist;
}
@@ -777,7 +749,7 @@
values.put(Audio.Media.IS_ALARM, alarms);
values.put(Audio.Media.IS_MUSIC, music);
values.put(Audio.Media.IS_PODCAST, podcasts);
- } else if (mFileType == MediaFile.FILE_TYPE_JPEG) {
+ } else if (mFileType == MediaFile.FILE_TYPE_JPEG && !mNoMedia) {
ExifInterface exif = null;
try {
exif = new ExifInterface(entry.mPath);
@@ -829,7 +801,16 @@
}
}
- Uri tableUri = entry.mTableUri;
+ Uri tableUri = mFilesUri;
+ if (!mNoMedia) {
+ if (MediaFile.isVideoFileType(mFileType)) {
+ tableUri = mVideoUri;
+ } else if (MediaFile.isImageFileType(mFileType)) {
+ tableUri = mImagesUri;
+ } else if (MediaFile.isAudioFileType(mFileType)) {
+ tableUri = mAudioUri;
+ }
+ }
Uri result = null;
if (rowId == 0) {
if (mMtpObjectHandle != 0) {
@@ -939,25 +920,6 @@
}
}
- public void addNoMediaFolder(String path) {
- ContentValues values = new ContentValues();
- values.put(MediaStore.Images.ImageColumns.DATA, "");
- String [] pathSpec = new String[] {path + '%'};
- try {
- // These tables have DELETE_FILE triggers that delete the file from the
- // sd card when deleting the database entry. We don't want to do this in
- // this case, since it would cause those files to be removed if a .nomedia
- // file was added after the fact, when in that case we only want the database
- // entries to be removed.
- mMediaProvider.update(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values,
- MediaStore.Images.ImageColumns.DATA + " LIKE ?", pathSpec);
- mMediaProvider.update(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values,
- MediaStore.Images.ImageColumns.DATA + " LIKE ?", pathSpec);
- } catch (RemoteException e) {
- throw new RuntimeException();
- }
- }
-
private int getFileTypeFromDrm(String path) {
if (!isDrmEnabled()) {
return 0;
@@ -1021,13 +983,13 @@
// Only consider entries with absolute path names.
// This allows storing URIs in the database without the
// media scanner removing them.
- if (path.startsWith("/")) {
+ if (path != null && path.startsWith("/")) {
String key = path;
if (mCaseInsensitivePaths) {
key = path.toLowerCase();
}
- FileCacheEntry entry = new FileCacheEntry(mFilesUri, rowId, path,
+ FileCacheEntry entry = new FileCacheEntry(rowId, path,
lastModified, format);
mFileCache.put(key, entry);
}
@@ -1238,13 +1200,71 @@
// always scan the file, so we can return the content://media Uri for existing files
return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
- false, true);
+ false, true, false);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
return null;
}
}
+ private static boolean isNoMediaFile(String path) {
+ File file = new File(path);
+ if (file.isDirectory()) return false;
+
+ // special case certain file names
+ // I use regionMatches() instead of substring() below
+ // to avoid memory allocation
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
+ // ignore those ._* files created by MacOS
+ if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
+ return true;
+ }
+
+ // ignore album art files created by Windows Media Player:
+ // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg
+ // and AlbumArt_{...}_Small.jpg
+ if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
+ if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
+ path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
+ return true;
+ }
+ int length = path.length() - lastSlash - 1;
+ if ((length == 17 && path.regionMatches(
+ true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
+ (length == 10
+ && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean isNoMediaPath(String path) {
+ if (path == null) return false;
+
+ // return true if file or any parent directory has name starting with a dot
+ if (path.indexOf("/.") >= 0) return true;
+
+ // now check to see if any parent directories have a ".nomedia" file
+ // start from 1 so we don't bother checking in the root directory
+ int offset = 1;
+ while (offset >= 0) {
+ int slashIndex = path.indexOf('/', offset);
+ if (slashIndex > offset) {
+ slashIndex++; // move past slash
+ File file = new File(path.substring(0, slashIndex) + ".nomedia");
+ if (file.exists()) {
+ // we have a .nomedia in one of the parent directories
+ return true;
+ }
+ }
+ offset = slashIndex;
+ }
+ return isNoMediaFile(path);
+ }
+
public void scanMtpFile(String path, String volumeName, int objectHandle, int format) {
initialize(volumeName);
MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
@@ -1289,7 +1309,7 @@
// always scan the file, so we can return the content://media Uri for existing files
mClient.doScanFile(path, mediaFileType.mimeType, lastModifiedSeconds, file.length(),
- (format == MtpConstants.FORMAT_ASSOCIATION), true);
+ (format == MtpConstants.FORMAT_ASSOCIATION), true, isNoMediaPath(path));
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
diff --git a/media/java/android/media/MediaScannerClient.java b/media/java/android/media/MediaScannerClient.java
index ac326ef..b326671 100644
--- a/media/java/android/media/MediaScannerClient.java
+++ b/media/java/android/media/MediaScannerClient.java
@@ -21,9 +21,8 @@
*/
public interface MediaScannerClient
{
- public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory);
-
- public void addNoMediaFolder(String path);
+ public void scanFile(String path, long lastModified, long fileSize,
+ boolean isDirectory, boolean noMedia);
/**
* Called by native code to return metadata extracted from media files.
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index b4a4689..b900671 100644
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -50,7 +50,8 @@
private final IContentProvider mMediaProvider;
private final String mVolumeName;
private final Uri mObjectsUri;
- private final String mMediaStoragePath;
+ private final String mMediaStoragePath; // path to primary storage
+ private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
// cached property groups for single properties
private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
@@ -67,9 +68,6 @@
private SharedPreferences mDeviceProperties;
private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
- // FIXME - this should be passed in via the constructor
- private final int mStorageID = 0x00010001;
-
private static final String[] ID_PROJECTION = new String[] {
Files.FileColumns._ID, // 0
};
@@ -85,17 +83,22 @@
};
private static final String[] OBJECT_INFO_PROJECTION = new String[] {
Files.FileColumns._ID, // 0
- Files.FileColumns.DATA, // 1
+ Files.FileColumns.STORAGE_ID, // 1
Files.FileColumns.FORMAT, // 2
Files.FileColumns.PARENT, // 3
- Files.FileColumns.SIZE, // 4
- Files.FileColumns.DATE_MODIFIED, // 5
+ Files.FileColumns.DATA, // 4
+ Files.FileColumns.SIZE, // 5
+ Files.FileColumns.DATE_MODIFIED, // 6
};
private static final String ID_WHERE = Files.FileColumns._ID + "=?";
private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND "
+ Files.FileColumns.FORMAT + "=?";
+ private static final String PARENT_STORAGE_WHERE = PARENT_WHERE + " AND "
+ + Files.FileColumns.STORAGE_ID + "=?";
+ private static final String PARENT_STORAGE_FORMAT_WHERE = PARENT_STORAGE_WHERE + " AND "
+ + Files.FileColumns.FORMAT + "=?";
private final MediaScanner mMediaScanner;
@@ -124,6 +127,14 @@
}
}
+ public void addStorage(MtpStorage storage) {
+ mStorageMap.put(storage.getPath(), storage);
+ }
+
+ public void removeStorage(MtpStorage storage) {
+ mStorageMap.remove(storage.getPath());
+ }
+
private void initDeviceProperties(Context context) {
final String devicePropertiesName = "device-properties";
mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
@@ -160,7 +171,7 @@
}
private int beginSendObject(String path, int format, int parent,
- int storage, long size, long modified) {
+ int storageId, long size, long modified) {
// first make sure the object does not exist
if (path != null) {
Cursor c = null;
@@ -185,7 +196,7 @@
values.put(Files.FileColumns.DATA, path);
values.put(Files.FileColumns.FORMAT, format);
values.put(Files.FileColumns.PARENT, parent);
- // storage is ignored for now
+ values.put(Files.FileColumns.STORAGE_ID, storageId);
values.put(Files.FileColumns.SIZE, size);
values.put(Files.FileColumns.DATE_MODIFIED, modified);
@@ -237,19 +248,35 @@
}
}
- private int[] getObjectList(int storageID, int format, int parent) {
- // we can ignore storageID until we support multiple storages
- Cursor c = null;
- try {
+ private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
+ if (storageID != 0) {
if (format != 0) {
- c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+ return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+ PARENT_STORAGE_FORMAT_WHERE,
+ new String[] { Integer.toString(parent), Integer.toString(storageID),
+ Integer.toString(format) }, null);
+ } else {
+ return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+ PARENT_STORAGE_WHERE, new String[]
+ { Integer.toString(parent), Integer.toString(storageID) }, null);
+ }
+ } else {
+ if (format != 0) {
+ return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
PARENT_FORMAT_WHERE,
new String[] { Integer.toString(parent), Integer.toString(format) },
null);
} else {
- c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
+ return mMediaProvider.query(mObjectsUri, ID_PROJECTION,
PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
}
+ }
+ }
+
+ private int[] getObjectList(int storageID, int format, int parent) {
+ Cursor c = null;
+ try {
+ c = createObjectQuery(storageID, format, parent);
if (c == null) {
return null;
}
@@ -273,18 +300,9 @@
}
private int getNumObjects(int storageID, int format, int parent) {
- // we can ignore storageID until we support multiple storages
Cursor c = null;
try {
- if (format != 0) {
- c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
- PARENT_FORMAT_WHERE,
- new String[] { Integer.toString(parent), Integer.toString(format) },
- null);
- } else {
- c = mMediaProvider.query(mObjectsUri, ID_PROJECTION,
- PARENT_WHERE, new String[] { Integer.toString(parent) }, null);
- }
+ c = createObjectQuery(storageID, format, parent);
if (c != null) {
return c.getCount();
}
@@ -508,7 +526,7 @@
}
}
- return propertyGroup.getPropertyList((int)handle, format, depth, mStorageID);
+ return propertyGroup.getPropertyList((int)handle, format, depth);
}
private int renameFile(int handle, String newName) {
@@ -631,12 +649,12 @@
c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
ID_WHERE, new String[] { Integer.toString(handle) }, null);
if (c != null && c.moveToNext()) {
- outStorageFormatParent[0] = mStorageID;
+ outStorageFormatParent[0] = c.getInt(1);
outStorageFormatParent[1] = c.getInt(2);
outStorageFormatParent[2] = c.getInt(3);
// extract name from path
- String path = c.getString(1);
+ String path = c.getString(4);
int lastSlash = path.lastIndexOf('/');
int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
int end = path.length();
@@ -646,8 +664,8 @@
path.getChars(start, end, outName, 0);
outName[end - start] = 0;
- outSizeModified[0] = c.getLong(4);
- outSizeModified[1] = c.getLong(5);
+ outSizeModified[0] = c.getLong(5);
+ outSizeModified[1] = c.getLong(6);
return true;
}
} catch (RemoteException e) {
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index fceedd2..b75b11a 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -93,7 +93,7 @@
switch (code) {
case MtpConstants.PROPERTY_STORAGE_ID:
- // no query needed until we support multiple storage units
+ column = Files.FileColumns.STORAGE_ID;
type = MtpConstants.TYPE_UINT32;
break;
case MtpConstants.PROPERTY_OBJECT_FORMAT:
@@ -134,6 +134,7 @@
break;
case MtpConstants.PROPERTY_PERSISTENT_UID:
// PUID is concatenation of storageID and object handle
+ column = Files.FileColumns.STORAGE_ID;
type = MtpConstants.TYPE_UINT128;
break;
case MtpConstants.PROPERTY_DURATION:
@@ -280,7 +281,7 @@
return path.substring(start, end);
}
- MtpPropertyList getPropertyList(int handle, int format, int depth, int storageID) {
+ MtpPropertyList getPropertyList(int handle, int format, int depth) {
//Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth);
if (depth > 1) {
// we only support depth 0 and 1
@@ -348,10 +349,6 @@
// handle some special cases
switch (propertyCode) {
- case MtpConstants.PROPERTY_STORAGE_ID:
- result.append(handle, propertyCode, MtpConstants.TYPE_UINT32,
- storageID);
- break;
case MtpConstants.PROPERTY_PROTECTION_STATUS:
// protection status is always 0
result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
@@ -398,7 +395,7 @@
break;
case MtpConstants.PROPERTY_PERSISTENT_UID:
// PUID is concatenation of storageID and object handle
- long puid = storageID;
+ long puid = c.getLong(column);
puid <<= 32;
puid += handle;
result.append(handle, propertyCode, MtpConstants.TYPE_UINT128, puid);
diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java
index 006fa6d..c065ca8 100644
--- a/media/java/android/mtp/MtpServer.java
+++ b/media/java/android/mtp/MtpServer.java
@@ -33,8 +33,8 @@
System.loadLibrary("media_jni");
}
- public MtpServer(MtpDatabase database, String storagePath, long reserveSpace) {
- native_setup(database, storagePath, reserveSpace);
+ public MtpServer(MtpDatabase database) {
+ native_setup(database);
}
public void start() {
@@ -65,18 +65,20 @@
native_set_ptp_mode(usePtp);
}
- // Used to disable MTP by removing all storage units.
- // This is done to disable access to file transfer when the device is locked.
- public void setLocked(boolean locked) {
- native_set_locked(locked);
+ public void addStorage(MtpStorage storage) {
+ native_add_storage(storage);
}
- private native final void native_setup(MtpDatabase database, String storagePath,
- long reserveSpace);
+ public void removeStorage(MtpStorage storage) {
+ native_remove_storage(storage.getStorageId());
+ }
+
+ private native final void native_setup(MtpDatabase database);
private native final void native_start();
private native final void native_stop();
private native final void native_send_object_added(int handle);
private native final void native_send_object_removed(int handle);
private native final void native_set_ptp_mode(boolean usePtp);
- private native final void native_set_locked(boolean locked);
+ private native final void native_add_storage(MtpStorage storage);
+ private native final void native_remove_storage(int storageId);
}
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
new file mode 100644
index 0000000..33146e7
--- /dev/null
+++ b/media/java/android/mtp/MtpStorage.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 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.mtp;
+
+/**
+ * This class represents a storage unit on an MTP device.
+ * Used only for MTP support in USB responder mode.
+ * MtpStorageInfo is used in MTP host mode
+ *
+ * @hide
+ */
+public class MtpStorage {
+
+ private final int mStorageId;
+ private final String mPath;
+ private final String mDescription;
+ private final long mReserveSpace;
+
+ public MtpStorage(int id, String path, String description, long reserveSpace) {
+ mStorageId = id;
+ mPath = path;
+ mDescription = description;
+ mReserveSpace = reserveSpace;
+ }
+
+ /**
+ * Returns the storage ID for the storage unit
+ *
+ * @return the storage ID
+ */
+ public final int getStorageId() {
+ return mStorageId;
+ }
+
+ /**
+ * Generates a storage ID for storage of given index.
+ * Index 0 is for primary external storage
+ *
+ * @return the storage ID
+ */
+ public static int getStorageId(int index) {
+ // storage ID is 0x00010001 for primary storage,
+ // then 0x00020001, 0x00030001, etc. for secondary storages
+ return ((index + 1) << 16) + 1;
+ }
+
+ /**
+ * Returns the file path for the storage unit's storage in the file system
+ *
+ * @return the storage file path
+ */
+ public final String getPath() {
+ return mPath;
+ }
+
+ /**
+ * Returns the description string for the storage unit
+ *
+ * @return the storage unit description
+ */
+ public final String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Returns the amount of space to reserve on the storage file system.
+ * This can be set to a non-zero value to prevent MTP from filling up the entire storage.
+ *
+ * @return the storage unit description
+ */
+ public final long getReserveSpace() {
+ return mReserveSpace;
+ }
+
+}
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 2a89a2a..2ddb287 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -27,9 +27,11 @@
libcamera_client \
libsqlite \
libmtp \
- libusbhost
+ libusbhost \
+ libexif
LOCAL_C_INCLUDES += \
+ external/jhead \
external/tremor/Tremor \
frameworks/base/core/jni \
frameworks/base/media/libmedia \
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
index a5176fa..19f3ca3 100644
--- a/media/jni/android_media_MediaScanner.cpp
+++ b/media/jni/android_media_MediaScanner.cpp
@@ -62,13 +62,11 @@
}
else {
mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",
- "(Ljava/lang/String;JJZ)V");
+ "(Ljava/lang/String;JJZZ)V");
mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",
"(Ljava/lang/String;Ljava/lang/String;)V");
mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",
"(Ljava/lang/String;)V");
- mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",
- "(Ljava/lang/String;)V");
}
}
@@ -79,13 +77,13 @@
// returns true if it succeeded, false if an exception occured in the Java code
virtual bool scanFile(const char* path, long long lastModified,
- long long fileSize, bool isDirectory)
+ long long fileSize, bool isDirectory, bool noMedia)
{
jstring pathStr;
if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
- fileSize, isDirectory);
+ fileSize, isDirectory, noMedia);
mEnv->DeleteLocalRef(pathStr);
return (!mEnv->ExceptionCheck());
@@ -117,26 +115,12 @@
return (!mEnv->ExceptionCheck());
}
- // returns true if it succeeded, false if an exception occured in the Java code
- virtual bool addNoMediaFolder(const char* path)
- {
- jstring pathStr;
- if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
-
- mEnv->CallVoidMethod(mClient, mAddNoMediaFolderMethodID, pathStr);
-
- mEnv->DeleteLocalRef(pathStr);
- return (!mEnv->ExceptionCheck());
- }
-
-
private:
JNIEnv *mEnv;
jobject mClient;
jmethodID mScanFileMethodID;
jmethodID mHandleStringTagMethodID;
jmethodID mSetMimeTypeMethodID;
- jmethodID mAddNoMediaFolderMethodID;
};
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 17d39e3..b78af44 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -29,11 +29,16 @@
#include "MtpDatabase.h"
#include "MtpDataPacket.h"
+#include "MtpObjectInfo.h"
#include "MtpProperty.h"
#include "MtpStringBuffer.h"
#include "MtpUtils.h"
#include "mtp.h"
+extern "C" {
+#include "jhead.h"
+}
+
using namespace android;
// ----------------------------------------------------------------------------
@@ -138,7 +143,9 @@
MtpDataPacket& packet);
virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle,
- MtpDataPacket& packet);
+ MtpObjectInfo& info);
+
+ virtual void* getThumbnail(MtpObjectHandle handle, size_t& outThumbSize);
virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle,
MtpString& outFilePath,
@@ -746,7 +753,7 @@
}
MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
- MtpDataPacket& packet) {
+ MtpObjectInfo& info) {
char date[20];
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -756,50 +763,88 @@
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
jint* intValues = env->GetIntArrayElements(mIntBuffer, 0);
- MtpStorageID storageID = intValues[0];
- MtpObjectFormat format = intValues[1];
- MtpObjectHandle parent = intValues[2];
+ info.mStorageID = intValues[0];
+ info.mFormat = intValues[1];
+ info.mParent = intValues[2];
env->ReleaseIntArrayElements(mIntBuffer, intValues, 0);
jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
uint64_t size = longValues[0];
- uint64_t modified = longValues[1];
+ info.mCompressedSize = (size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size);
+ info.mDateModified = longValues[1];
env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
-// int associationType = (format == MTP_FORMAT_ASSOCIATION ?
+// info.mAssociationType = (format == MTP_FORMAT_ASSOCIATION ?
// MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
// MTP_ASSOCIATION_TYPE_UNDEFINED);
- int associationType = MTP_ASSOCIATION_TYPE_UNDEFINED;
-
- packet.putUInt32(storageID);
- packet.putUInt16(format);
- packet.putUInt16(0); // protection status
- packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size));
- packet.putUInt16(0); // thumb format
- packet.putUInt32(0); // thumb compressed size
- packet.putUInt32(0); // thumb pix width
- packet.putUInt32(0); // thumb pix height
- packet.putUInt32(0); // image pix width
- packet.putUInt32(0); // image pix height
- packet.putUInt32(0); // image bit depth
- packet.putUInt32(parent);
- packet.putUInt16(associationType);
- packet.putUInt32(0); // association desc
- packet.putUInt32(0); // sequence number
+ info.mAssociationType = MTP_ASSOCIATION_TYPE_UNDEFINED;
jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
- packet.putString(str); // file name
+ MtpString temp(str);
+ info.mName = strdup((const char *)temp);
env->ReleaseCharArrayElements(mStringBuffer, str, 0);
- packet.putEmptyString();
- formatDateTime(modified, date, sizeof(date));
- packet.putString(date); // date modified
- packet.putEmptyString(); // keywords
+ // read EXIF data for thumbnail information
+ if (info.mFormat == MTP_FORMAT_EXIF_JPEG || info.mFormat == MTP_FORMAT_JFIF) {
+ MtpString path;
+ int64_t length;
+ MtpObjectFormat format;
+ if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK) {
+ ResetJpgfile();
+ // Start with an empty image information structure.
+ memset(&ImageInfo, 0, sizeof(ImageInfo));
+ ImageInfo.FlashUsed = -1;
+ ImageInfo.MeteringMode = -1;
+ ImageInfo.Whitebalance = -1;
+ strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX);
+ if (ReadJpegFile((const char*)path, READ_METADATA)) {
+ Section_t* section = FindSection(M_EXIF);
+ if (section) {
+ info.mThumbCompressedSize = ImageInfo.ThumbnailSize;
+ info.mThumbFormat = MTP_FORMAT_EXIF_JPEG;
+ info.mImagePixWidth = ImageInfo.Width;
+ info.mImagePixHeight = ImageInfo.Height;
+ }
+ }
+ DiscardData();
+ }
+ }
checkAndClearExceptionFromCallback(env, __FUNCTION__);
return MTP_RESPONSE_OK;
}
+void* MyMtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) {
+ MtpString path;
+ int64_t length;
+ MtpObjectFormat format;
+ void* result = NULL;
+ outThumbSize = 0;
+
+ if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK
+ && (format == MTP_FORMAT_EXIF_JPEG || format == MTP_FORMAT_JFIF)) {
+ ResetJpgfile();
+ // Start with an empty image information structure.
+ memset(&ImageInfo, 0, sizeof(ImageInfo));
+ ImageInfo.FlashUsed = -1;
+ ImageInfo.MeteringMode = -1;
+ ImageInfo.Whitebalance = -1;
+ strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX);
+ if (ReadJpegFile((const char*)path, READ_METADATA)) {
+ Section_t* section = FindSection(M_EXIF);
+ if (section) {
+ outThumbSize = ImageInfo.ThumbnailSize;
+ result = malloc(outThumbSize);
+ if (result)
+ memcpy(result, section->Data + ImageInfo.ThumbnailOffset + 8, outThumbSize);
+ }
+ DiscardData();
+ }
+ }
+
+ return result;
+}
+
MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
MtpString& outFilePath,
int64_t& outFileLength,
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index e025ef1..c55189f 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -39,6 +39,17 @@
using namespace android;
+// MtpStorage class
+jclass clazz_MtpStorage;
+
+// MtpStorage fields
+static jfieldID field_MtpStorage_storageId;
+static jfieldID field_MtpStorage_path;
+static jfieldID field_MtpStorage_description;
+static jfieldID field_MtpStorage_reserveSpace;
+
+static Mutex sMutex;
+
// ----------------------------------------------------------------------------
// in android_mtp_MtpDatabase.cpp
@@ -57,70 +68,77 @@
private:
MtpDatabase* mDatabase;
MtpServer* mServer;
- MtpStorage* mStorage;
- Mutex mMutex;
+ MtpStorageList mStorageList;
bool mUsePtp;
- bool mLocked;
int mFd;
public:
- MtpThread(MtpDatabase* database, MtpStorage* storage)
+ MtpThread(MtpDatabase* database)
: mDatabase(database),
mServer(NULL),
- mStorage(storage),
mUsePtp(false),
- mLocked(false),
mFd(-1)
{
}
virtual ~MtpThread() {
- delete mStorage;
}
void setPtpMode(bool usePtp) {
- mMutex.lock();
mUsePtp = usePtp;
- mMutex.unlock();
}
- void setLocked(bool locked) {
- mMutex.lock();
- if (locked != mLocked) {
- if (mServer) {
- if (locked)
- mServer->removeStorage(mStorage);
- else
- mServer->addStorage(mStorage);
+ void addStorage(MtpStorage *storage) {
+ mStorageList.push(storage);
+ if (mServer)
+ mServer->addStorage(storage);
+ }
+
+ void removeStorage(MtpStorageID id) {
+ MtpStorage* storage = mServer->getStorage(id);
+ if (storage) {
+ for (int i = 0; i < mStorageList.size(); i++) {
+ if (mStorageList[i] == storage) {
+ mStorageList.removeAt(i);
+ break;
+ }
}
- mLocked = locked;
+ if (mServer)
+ mServer->removeStorage(storage);
+ delete storage;
}
- mMutex.unlock();
+ }
+
+ void start() {
+ run("MtpThread");
}
virtual bool threadLoop() {
- mMutex.lock();
+ sMutex.lock();
+
mFd = open("/dev/mtp_usb", O_RDWR);
if (mFd >= 0) {
ioctl(mFd, MTP_SET_INTERFACE_MODE,
(mUsePtp ? MTP_INTERFACE_MODE_PTP : MTP_INTERFACE_MODE_MTP));
mServer = new MtpServer(mFd, mDatabase, AID_MEDIA_RW, 0664, 0775);
- if (!mLocked)
- mServer->addStorage(mStorage);
-
- mMutex.unlock();
- mServer->run();
- mMutex.lock();
-
- close(mFd);
- mFd = -1;
- delete mServer;
- mServer = NULL;
+ for (int i = 0; i < mStorageList.size(); i++) {
+ mServer->addStorage(mStorageList[i]);
+ }
} else {
LOGE("could not open MTP driver, errno: %d", errno);
}
- mMutex.unlock();
+
+ sMutex.unlock();
+ mServer->run();
+ sMutex.lock();
+
+ close(mFd);
+ mFd = -1;
+ delete mServer;
+ mServer = NULL;
+
+ sMutex.unlock();
// delay a bit before retrying to avoid excessive spin
if (!exitPending()) {
sleep(1);
@@ -130,17 +148,13 @@
}
void sendObjectAdded(MtpObjectHandle handle) {
- mMutex.lock();
if (mServer)
mServer->sendObjectAdded(handle);
- mMutex.unlock();
}
void sendObjectRemoved(MtpObjectHandle handle) {
- mMutex.lock();
if (mServer)
mServer->sendObjectRemoved(handle);
- mMutex.unlock();
}
};
@@ -150,18 +164,11 @@
#endif // HAVE_ANDROID_OS
static void
-android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase,
- jstring storagePath, jlong reserveSpace)
+android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase)
{
#ifdef HAVE_ANDROID_OS
- MtpDatabase* database = getMtpDatabase(env, javaDatabase);
- const char *storagePathStr = env->GetStringUTFChars(storagePath, NULL);
-
// create the thread and assign it to the smart pointer
- MtpStorage* storage = new MtpStorage(MTP_FIRST_STORAGE_ID, storagePathStr, reserveSpace);
- sThread = new MtpThread(database, storage);
-
- env->ReleaseStringUTFChars(storagePath, storagePathStr);
+ sThread = new MtpThread(getMtpDatabase(env, javaDatabase));
#endif
}
@@ -169,9 +176,11 @@
android_mtp_MtpServer_start(JNIEnv *env, jobject thiz)
{
#ifdef HAVE_ANDROID_OS
+ sMutex.lock();
MtpThread *thread = sThread.get();
if (thread)
- thread->run("MtpThread");
+ thread->start();
+ sMutex.unlock();
#endif // HAVE_ANDROID_OS
}
@@ -179,11 +188,13 @@
android_mtp_MtpServer_stop(JNIEnv *env, jobject thiz)
{
#ifdef HAVE_ANDROID_OS
+ sMutex.lock();
MtpThread *thread = sThread.get();
if (thread) {
thread->requestExitAndWait();
sThread = NULL;
}
+ sMutex.unlock();
#endif
}
@@ -191,9 +202,11 @@
android_mtp_MtpServer_send_object_added(JNIEnv *env, jobject thiz, jint handle)
{
#ifdef HAVE_ANDROID_OS
+ sMutex.lock();
MtpThread *thread = sThread.get();
if (thread)
thread->sendObjectAdded(handle);
+ sMutex.unlock();
#endif
}
@@ -201,9 +214,11 @@
android_mtp_MtpServer_send_object_removed(JNIEnv *env, jobject thiz, jint handle)
{
#ifdef HAVE_ANDROID_OS
+ sMutex.lock();
MtpThread *thread = sThread.get();
if (thread)
thread->sendObjectRemoved(handle);
+ sMutex.unlock();
#endif
}
@@ -211,33 +226,68 @@
android_mtp_MtpServer_set_ptp_mode(JNIEnv *env, jobject thiz, jboolean usePtp)
{
#ifdef HAVE_ANDROID_OS
+ sMutex.lock();
MtpThread *thread = sThread.get();
if (thread)
thread->setPtpMode(usePtp);
+ sMutex.unlock();
#endif
}
static void
-android_mtp_MtpServer_set_locked(JNIEnv *env, jobject thiz, jboolean locked)
+android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage)
{
#ifdef HAVE_ANDROID_OS
+ sMutex.lock();
+ MtpThread *thread = sThread.get();
+ if (thread) {
+ jint storageID = env->GetIntField(jstorage, field_MtpStorage_storageId);
+ jstring path = (jstring)env->GetObjectField(jstorage, field_MtpStorage_path);
+ jstring description = (jstring)env->GetObjectField(jstorage, field_MtpStorage_description);
+ jlong reserveSpace = env->GetLongField(jstorage, field_MtpStorage_reserveSpace);
+
+ const char *pathStr = env->GetStringUTFChars(path, NULL);
+ const char *descriptionStr = env->GetStringUTFChars(description, NULL);
+
+ MtpStorage* storage = new MtpStorage(storageID, pathStr, descriptionStr, reserveSpace);
+ thread->addStorage(storage);
+
+ env->ReleaseStringUTFChars(path, pathStr);
+ env->ReleaseStringUTFChars(description, descriptionStr);
+ } else {
+ LOGE("MtpThread is null in add_storage");
+ }
+ sMutex.unlock();
+#endif
+}
+
+static void
+android_mtp_MtpServer_remove_storage(JNIEnv *env, jobject thiz, jint storageId)
+{
+#ifdef HAVE_ANDROID_OS
+ sMutex.lock();
MtpThread *thread = sThread.get();
if (thread)
- thread->setLocked(locked);
+ thread->removeStorage(storageId);
+ else
+ LOGE("MtpThread is null in remove_storage");
+ sMutex.unlock();
#endif
}
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
- {"native_setup", "(Landroid/mtp/MtpDatabase;Ljava/lang/String;J)V",
+ {"native_setup", "(Landroid/mtp/MtpDatabase;)V",
(void *)android_mtp_MtpServer_setup},
{"native_start", "()V", (void *)android_mtp_MtpServer_start},
{"native_stop", "()V", (void *)android_mtp_MtpServer_stop},
{"native_send_object_added", "(I)V", (void *)android_mtp_MtpServer_send_object_added},
{"native_send_object_removed", "(I)V", (void *)android_mtp_MtpServer_send_object_removed},
{"native_set_ptp_mode", "(Z)V", (void *)android_mtp_MtpServer_set_ptp_mode},
- {"native_set_locked", "(Z)V", (void *)android_mtp_MtpServer_set_locked},
+ {"native_add_storage", "(Landroid/mtp/MtpStorage;)V",
+ (void *)android_mtp_MtpServer_add_storage},
+ {"native_remove_storage", "(I)V", (void *)android_mtp_MtpServer_remove_storage},
};
static const char* const kClassPathName = "android/mtp/MtpServer";
@@ -246,6 +296,33 @@
{
jclass clazz;
+ clazz = env->FindClass("android/mtp/MtpStorage");
+ if (clazz == NULL) {
+ LOGE("Can't find android/mtp/MtpStorage");
+ return -1;
+ }
+ field_MtpStorage_storageId = env->GetFieldID(clazz, "mStorageId", "I");
+ if (field_MtpStorage_storageId == NULL) {
+ LOGE("Can't find MtpStorage.mStorageId");
+ return -1;
+ }
+ field_MtpStorage_path = env->GetFieldID(clazz, "mPath", "Ljava/lang/String;");
+ if (field_MtpStorage_path == NULL) {
+ LOGE("Can't find MtpStorage.mPath");
+ return -1;
+ }
+ field_MtpStorage_description = env->GetFieldID(clazz, "mDescription", "Ljava/lang/String;");
+ if (field_MtpStorage_description == NULL) {
+ LOGE("Can't find MtpStorage.mDescription");
+ return -1;
+ }
+ field_MtpStorage_reserveSpace = env->GetFieldID(clazz, "mReserveSpace", "J");
+ if (field_MtpStorage_reserveSpace == NULL) {
+ LOGE("Can't find MtpStorage.mStorageId");
+ return -1;
+ }
+ clazz_MtpStorage = (jclass)env->NewGlobalRef(clazz);
+
clazz = env->FindClass("android/mtp/MtpServer");
if (clazz == NULL) {
LOGE("Can't find android/mtp/MtpServer");
diff --git a/media/libmedia/MediaScanner.cpp b/media/libmedia/MediaScanner.cpp
index 5ec573e..28c86426 100644
--- a/media/libmedia/MediaScanner.cpp
+++ b/media/libmedia/MediaScanner.cpp
@@ -70,8 +70,7 @@
client.setLocale(locale());
status_t result =
- doProcessDirectory(
- pathBuffer, pathRemaining, client, exceptionCheck, exceptionEnv);
+ doProcessDirectory(pathBuffer, pathRemaining, client, false, exceptionCheck, exceptionEnv);
free(pathBuffer);
@@ -80,20 +79,18 @@
status_t MediaScanner::doProcessDirectory(
char *path, int pathRemaining, MediaScannerClient &client,
- ExceptionCheck exceptionCheck, void *exceptionEnv) {
+ bool noMedia, ExceptionCheck exceptionCheck, void *exceptionEnv) {
// place to copy file or directory name
char* fileSpot = path + strlen(path);
struct dirent* entry;
struct stat statbuf;
- // ignore directories that contain a ".nomedia" file
+ // Treat all files as non-media in directories that contain a ".nomedia" file
if (pathRemaining >= 8 /* strlen(".nomedia") */ ) {
strcpy(fileSpot, ".nomedia");
if (access(path, F_OK) == 0) {
- LOGD("found .nomedia, skipping directory\n");
- fileSpot[0] = 0;
- client.addNoMediaFolder(path);
- return OK;
+ LOGD("found .nomedia, setting noMedia flag\n");
+ noMedia = true;
}
// restore path
@@ -138,19 +135,21 @@
}
if (type == DT_REG || type == DT_DIR) {
if (type == DT_DIR) {
- // ignore directories with a name that starts with '.'
+ bool childNoMedia = noMedia;
+ // set noMedia flag on directories with a name that starts with '.'
// for example, the Mac ".Trashes" directory
- if (name[0] == '.') continue;
+ if (name[0] == '.')
+ childNoMedia = true;
// report the directory to the client
if (stat(path, &statbuf) == 0) {
- client.scanFile(path, statbuf.st_mtime, 0, true);
+ client.scanFile(path, statbuf.st_mtime, 0, true, childNoMedia);
}
// and now process its contents
strcat(fileSpot, "/");
int err = doProcessDirectory(path, pathRemaining - nameLength - 1, client,
- exceptionCheck, exceptionEnv);
+ childNoMedia, exceptionCheck, exceptionEnv);
if (err) {
// pass exceptions up - ignore other errors
if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
@@ -159,7 +158,7 @@
}
} else {
stat(path, &statbuf);
- client.scanFile(path, statbuf.st_mtime, statbuf.st_size, false);
+ client.scanFile(path, statbuf.st_mtime, statbuf.st_size, false, noMedia);
if (exceptionCheck && exceptionCheck(exceptionEnv)) goto failure;
}
}
diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp
index 0b0c80d..817eac05 100644
--- a/media/mtp/MtpDataPacket.cpp
+++ b/media/mtp/MtpDataPacket.cpp
@@ -388,6 +388,16 @@
int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
return (ret < 0 ? ret : 0);
}
+
+int MtpDataPacket::writeData(int fd, void* data, uint32_t length) {
+ MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length + MTP_CONTAINER_HEADER_SIZE);
+ MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+ int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+ if (ret == MTP_CONTAINER_HEADER_SIZE)
+ ret = ::write(fd, data, length);
+ return (ret < 0 ? ret : 0);
+}
+
#endif // MTP_DEVICE
#ifdef MTP_HOST
diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h
index 577cea1..8a08948 100644
--- a/media/mtp/MtpDataPacket.h
+++ b/media/mtp/MtpDataPacket.h
@@ -100,6 +100,7 @@
// write our data to the given file descriptor
int write(int fd);
int writeDataHeader(int fd, uint32_t length);
+ int writeData(int fd, void* data, uint32_t length);
#endif
#ifdef MTP_HOST
diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h
index 4d9a1ae..4e6ac7a 100644
--- a/media/mtp/MtpDatabase.h
+++ b/media/mtp/MtpDatabase.h
@@ -23,6 +23,7 @@
class MtpDataPacket;
class MtpProperty;
+class MtpObjectInfo;
class MtpDatabase {
public:
@@ -81,7 +82,9 @@
MtpDataPacket& packet) = 0;
virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle,
- MtpDataPacket& packet) = 0;
+ MtpObjectInfo& info) = 0;
+
+ virtual void* getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) = 0;
virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle,
MtpString& outFilePath,
diff --git a/media/mtp/MtpDebug.cpp b/media/mtp/MtpDebug.cpp
index 1668ecf..9f3037d 100644
--- a/media/mtp/MtpDebug.cpp
+++ b/media/mtp/MtpDebug.cpp
@@ -63,6 +63,12 @@
{ "MTP_OPERATION_GET_OBJECT_REFERENCES", 0x9810 },
{ "MTP_OPERATION_SET_OBJECT_REFERENCES", 0x9811 },
{ "MTP_OPERATION_SKIP", 0x9820 },
+ // android extensions
+ { "MTP_OPERATION_GET_PARTIAL_OBJECT_64", 0x95C1 },
+ { "MTP_OPERATION_SEND_PARTIAL_OBJECT", 0x95C2 },
+ { "MTP_OPERATION_TRUNCATE_OBJECT", 0x95C3 },
+ { "MTP_OPERATION_BEGIN_EDIT_OBJECT", 0x95C4 },
+ { "MTP_OPERATION_END_EDIT_OBJECT", 0x95C5 },
{ 0, 0 },
};
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 37e02a3..4a8fd3e 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -30,6 +30,7 @@
#include "MtpDebug.h"
#include "MtpDatabase.h"
+#include "MtpObjectInfo.h"
#include "MtpProperty.h"
#include "MtpServer.h"
#include "MtpStorage.h"
@@ -49,7 +50,7 @@
MTP_OPERATION_GET_OBJECT_HANDLES,
MTP_OPERATION_GET_OBJECT_INFO,
MTP_OPERATION_GET_OBJECT,
-// MTP_OPERATION_GET_THUMB,
+ MTP_OPERATION_GET_THUMB,
MTP_OPERATION_DELETE_OBJECT,
MTP_OPERATION_SEND_OBJECT_INFO,
MTP_OPERATION_SEND_OBJECT,
@@ -79,6 +80,12 @@
MTP_OPERATION_GET_OBJECT_REFERENCES,
MTP_OPERATION_SET_OBJECT_REFERENCES,
// MTP_OPERATION_SKIP,
+ // Android extension for direct file IO
+ MTP_OPERATION_GET_PARTIAL_OBJECT_64,
+ MTP_OPERATION_SEND_PARTIAL_OBJECT,
+ MTP_OPERATION_TRUNCATE_OBJECT,
+ MTP_OPERATION_BEGIN_EDIT_OBJECT,
+ MTP_OPERATION_END_EDIT_OBJECT,
};
static const MtpEventCode kSupportedEventCodes[] = {
@@ -218,6 +225,15 @@
}
}
+ // commit any open edits
+ int count = mObjectEditList.size();
+ for (int i = 0; i < count; i++) {
+ ObjectEdit* edit = mObjectEditList[i];
+ commitEdit(edit);
+ delete edit;
+ }
+ mObjectEditList.clear();
+
if (mSessionOpen)
mDatabase->sessionEnded();
}
@@ -252,6 +268,39 @@
}
}
+void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path,
+ uint64_t size, MtpObjectFormat format, int fd) {
+ ObjectEdit* edit = new ObjectEdit(handle, path, size, format, fd);
+ mObjectEditList.add(edit);
+}
+
+MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) {
+ int count = mObjectEditList.size();
+ for (int i = 0; i < count; i++) {
+ ObjectEdit* edit = mObjectEditList[i];
+ if (edit->mHandle == handle) return edit;
+ }
+ return NULL;
+}
+
+void MtpServer::removeEditObject(MtpObjectHandle handle) {
+ int count = mObjectEditList.size();
+ for (int i = 0; i < count; i++) {
+ ObjectEdit* edit = mObjectEditList[i];
+ if (edit->mHandle == handle) {
+ delete edit;
+ mObjectEditList.removeAt(i);
+ return;
+ }
+ }
+ LOGE("ObjectEdit not found in removeEditObject");
+}
+
+void MtpServer::commitEdit(ObjectEdit* edit) {
+ mDatabase->endSendObject((const char *)edit->mPath, edit->mHandle, edit->mFormat, true);
+}
+
+
bool MtpServer::handleRequest() {
Mutex::Autolock autoLock(mMutex);
@@ -321,8 +370,12 @@
case MTP_OPERATION_GET_OBJECT:
response = doGetObject();
break;
+ case MTP_OPERATION_GET_THUMB:
+ response = doGetThumb();
+ break;
case MTP_OPERATION_GET_PARTIAL_OBJECT:
- response = doGetPartialObject();
+ case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
+ response = doGetPartialObject(operation);
break;
case MTP_OPERATION_SEND_OBJECT_INFO:
response = doSendObjectInfo();
@@ -339,6 +392,18 @@
case MTP_OPERATION_GET_DEVICE_PROP_DESC:
response = doGetDevicePropDesc();
break;
+ case MTP_OPERATION_SEND_PARTIAL_OBJECT:
+ response = doSendPartialObject();
+ break;
+ case MTP_OPERATION_TRUNCATE_OBJECT:
+ response = doTruncateObject();
+ break;
+ case MTP_OPERATION_BEGIN_EDIT_OBJECT:
+ response = doBeginEditObject();
+ break;
+ case MTP_OPERATION_END_EDIT_OBJECT:
+ response = doEndEditObject();
+ break;
default:
LOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
@@ -363,7 +428,7 @@
mData.putUInt16(MTP_STANDARD_VERSION);
mData.putUInt32(6); // MTP Vendor Extension ID
mData.putUInt16(MTP_STANDARD_VERSION);
- string.set("microsoft.com: 1.0;");
+ string.set("microsoft.com: 1.0; android.com: 1.0;");
mData.putString(string); // MTP Extensions
mData.putUInt16(0); //Functional Mode
mData.putAUInt16(kSupportedOperationCodes,
@@ -601,7 +666,40 @@
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpObjectHandle handle = mRequest.getParameter(1);
- return mDatabase->getObjectInfo(handle, mData);
+ MtpObjectInfo info(handle);
+ MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
+ if (result == MTP_RESPONSE_OK) {
+ char date[20];
+
+ mData.putUInt32(info.mStorageID);
+ mData.putUInt16(info.mFormat);
+ mData.putUInt16(info.mProtectionStatus);
+
+ // if object is being edited the database size may be out of date
+ uint32_t size = info.mCompressedSize;
+ ObjectEdit* edit = getEditObject(handle);
+ if (edit)
+ size = (edit->mSize > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->mSize);
+ mData.putUInt32(size);
+
+ mData.putUInt16(info.mThumbFormat);
+ mData.putUInt32(info.mThumbCompressedSize);
+ mData.putUInt32(info.mThumbPixWidth);
+ mData.putUInt32(info.mThumbPixHeight);
+ mData.putUInt32(info.mImagePixWidth);
+ mData.putUInt32(info.mImagePixHeight);
+ mData.putUInt32(info.mImagePixDepth);
+ mData.putUInt32(info.mParent);
+ mData.putUInt16(info.mAssociationType);
+ mData.putUInt32(info.mAssociationDesc);
+ mData.putUInt32(info.mSequenceNumber);
+ mData.putString(info.mName);
+ mData.putEmptyString(); // date created
+ formatDateTime(info.mDateModified, date, sizeof(date));
+ mData.putString(date); // date modified
+ mData.putEmptyString(); // keywords
+ }
+ return result;
}
MtpResponseCode MtpServer::doGetObject() {
@@ -641,12 +739,38 @@
return MTP_RESPONSE_OK;
}
-MtpResponseCode MtpServer::doGetPartialObject() {
+MtpResponseCode MtpServer::doGetThumb() {
+ MtpObjectHandle handle = mRequest.getParameter(1);
+ size_t thumbSize;
+ void* thumb = mDatabase->getThumbnail(handle, thumbSize);
+ if (thumb) {
+ // send data
+ mData.setOperationCode(mRequest.getOperationCode());
+ mData.setTransactionID(mRequest.getTransactionID());
+ mData.writeData(mFD, thumb, thumbSize);
+ free(thumb);
+ return MTP_RESPONSE_OK;
+ } else {
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+}
+
+MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpObjectHandle handle = mRequest.getParameter(1);
- uint32_t offset = mRequest.getParameter(2);
- uint32_t length = mRequest.getParameter(3);
+ uint64_t offset;
+ uint32_t length;
+ offset = mRequest.getParameter(2);
+ if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) {
+ // android extension with 64 bit offset
+ uint64_t offset2 = mRequest.getParameter(3);
+ offset = offset | (offset2 << 32);
+ length = mRequest.getParameter(4);
+ } else {
+ // standard GetPartialObject
+ length = mRequest.getParameter(3);
+ }
MtpString pathBuf;
int64_t fileLength;
MtpObjectFormat format;
@@ -933,4 +1057,113 @@
return MTP_RESPONSE_OK;
}
+MtpResponseCode MtpServer::doSendPartialObject() {
+ if (!hasStorage())
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ MtpObjectHandle handle = mRequest.getParameter(1);
+ uint64_t offset = mRequest.getParameter(2);
+ uint64_t offset2 = mRequest.getParameter(3);
+ offset = offset | (offset2 << 32);
+ uint32_t length = mRequest.getParameter(4);
+
+ ObjectEdit* edit = getEditObject(handle);
+ if (!edit) {
+ LOGE("object not open for edit in doSendPartialObject");
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ // can't start writing past the end of the file
+ if (offset > edit->mSize) {
+ LOGD("writing past end of object, offset: %lld, edit->mSize: %lld", offset, edit->mSize);
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ // read the header
+ int ret = mData.readDataHeader(mFD);
+ // FIXME - check for errors here.
+
+ // reset so we don't attempt to send this back
+ mData.reset();
+
+ const char* filePath = (const char *)edit->mPath;
+ LOGV("receiving partial %s %lld %ld\n", filePath, offset, length);
+ mtp_file_range mfr;
+ mfr.fd = edit->mFD;
+ mfr.offset = offset;
+ mfr.length = length;
+
+ // transfer the file
+ ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
+ LOGV("MTP_RECEIVE_FILE returned %d", ret);
+ if (ret < 0) {
+ mResponse.setParameter(1, 0);
+ if (errno == ECANCELED)
+ return MTP_RESPONSE_TRANSACTION_CANCELLED;
+ else
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+ mResponse.setParameter(1, length);
+ uint64_t end = offset + length;
+ if (end > edit->mSize) {
+ edit->mSize = end;
+ }
+ return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doTruncateObject() {
+ MtpObjectHandle handle = mRequest.getParameter(1);
+ ObjectEdit* edit = getEditObject(handle);
+ if (!edit) {
+ LOGE("object not open for edit in doTruncateObject");
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ uint64_t offset = mRequest.getParameter(2);
+ uint64_t offset2 = mRequest.getParameter(3);
+ offset |= (offset2 << 32);
+ if (ftruncate(edit->mFD, offset) != 0) {
+ return MTP_RESPONSE_GENERAL_ERROR;
+ } else {
+ edit->mSize = offset;
+ return MTP_RESPONSE_OK;
+ }
+}
+
+MtpResponseCode MtpServer::doBeginEditObject() {
+ MtpObjectHandle handle = mRequest.getParameter(1);
+ if (getEditObject(handle)) {
+ LOGE("object already open for edit in doBeginEditObject");
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ MtpString path;
+ int64_t fileLength;
+ MtpObjectFormat format;
+ int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
+ if (result != MTP_RESPONSE_OK)
+ return result;
+
+ int fd = open((const char *)path, O_RDWR | O_EXCL);
+ if (fd < 0) {
+ LOGE("open failed for %s in doBeginEditObject (%d)", (const char *)path, errno);
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ addEditObject(handle, path, fileLength, format, fd);
+ return MTP_RESPONSE_OK;
+}
+
+MtpResponseCode MtpServer::doEndEditObject() {
+ MtpObjectHandle handle = mRequest.getParameter(1);
+ ObjectEdit* edit = getEditObject(handle);
+ if (!edit) {
+ LOGE("object not open for edit in doEndEditObject");
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ commitEdit(edit);
+ removeEditObject(handle);
+ return MTP_RESPONSE_OK;
+}
+
} // namespace android
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
index 1efa715..859a18e 100644
--- a/media/mtp/MtpServer.h
+++ b/media/mtp/MtpServer.h
@@ -65,11 +65,35 @@
Mutex mMutex;
+ // represents an MTP object that is being edited using the android extensions
+ // for direct editing (BeginEditObject, SendPartialObject, TruncateObject and EndEditObject)
+ class ObjectEdit {
+ public:
+ MtpObjectHandle mHandle;
+ MtpString mPath;
+ uint64_t mSize;
+ MtpObjectFormat mFormat;
+ int mFD;
+
+ ObjectEdit(MtpObjectHandle handle, const char* path, uint64_t size,
+ MtpObjectFormat format, int fd)
+ : mHandle(handle), mPath(path), mSize(size), mFormat(format), mFD(fd) {
+ }
+
+ virtual ~ObjectEdit() {
+ close(mFD);
+ }
+ };
+ Vector<ObjectEdit*> mObjectEditList;
+
public:
MtpServer(int fd, MtpDatabase* database,
int fileGroup, int filePerm, int directoryPerm);
virtual ~MtpServer();
+ MtpStorage* getStorage(MtpStorageID id);
+ inline bool hasStorage() { return mStorages.size() > 0; }
+ bool hasStorage(MtpStorageID id);
void addStorage(MtpStorage* storage);
void removeStorage(MtpStorage* storage);
@@ -79,13 +103,16 @@
void sendObjectRemoved(MtpObjectHandle handle);
private:
- MtpStorage* getStorage(MtpStorageID id);
- inline bool hasStorage() { return mStorages.size() > 0; }
- bool hasStorage(MtpStorageID id);
void sendStoreAdded(MtpStorageID id);
void sendStoreRemoved(MtpStorageID id);
void sendEvent(MtpEventCode code, uint32_t param1);
+ void addEditObject(MtpObjectHandle handle, MtpString& path,
+ uint64_t size, MtpObjectFormat format, int fd);
+ ObjectEdit* getEditObject(MtpObjectHandle handle);
+ void removeEditObject(MtpObjectHandle handle);
+ void commitEdit(ObjectEdit* edit);
+
bool handleRequest();
MtpResponseCode doGetDeviceInfo();
@@ -106,12 +133,17 @@
MtpResponseCode doGetObjectPropList();
MtpResponseCode doGetObjectInfo();
MtpResponseCode doGetObject();
- MtpResponseCode doGetPartialObject();
+ MtpResponseCode doGetThumb();
+ MtpResponseCode doGetPartialObject(MtpOperationCode operation);
MtpResponseCode doSendObjectInfo();
MtpResponseCode doSendObject();
MtpResponseCode doDeleteObject();
MtpResponseCode doGetObjectPropDesc();
MtpResponseCode doGetDevicePropDesc();
+ MtpResponseCode doSendPartialObject();
+ MtpResponseCode doTruncateObject();
+ MtpResponseCode doBeginEditObject();
+ MtpResponseCode doEndEditObject();
};
}; // namespace android
diff --git a/media/mtp/MtpStorage.cpp b/media/mtp/MtpStorage.cpp
index 6cb88b3..fff0b5f 100644
--- a/media/mtp/MtpStorage.cpp
+++ b/media/mtp/MtpStorage.cpp
@@ -32,9 +32,11 @@
namespace android {
-MtpStorage::MtpStorage(MtpStorageID id, const char* filePath, uint64_t reserveSpace)
+MtpStorage::MtpStorage(MtpStorageID id, const char* filePath,
+ const char* description, uint64_t reserveSpace)
: mStorageID(id),
mFilePath(filePath),
+ mDescription(description),
mMaxCapacity(0),
mReserveSpace(reserveSpace)
{
@@ -75,7 +77,7 @@
}
const char* MtpStorage::getDescription() const {
- return "Device Storage";
+ return (const char *)mDescription;
}
} // namespace android
diff --git a/media/mtp/MtpStorage.h b/media/mtp/MtpStorage.h
index 858c9d3..d6ad25f 100644
--- a/media/mtp/MtpStorage.h
+++ b/media/mtp/MtpStorage.h
@@ -29,13 +29,14 @@
private:
MtpStorageID mStorageID;
MtpString mFilePath;
+ MtpString mDescription;
uint64_t mMaxCapacity;
// amount of free space to leave unallocated
uint64_t mReserveSpace;
public:
MtpStorage(MtpStorageID id, const char* filePath,
- uint64_t reserveSpace);
+ const char* description, uint64_t reserveSpace);
virtual ~MtpStorage();
inline MtpStorageID getStorageID() const { return mStorageID; }
diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h
index 6fedc16..d270df5 100644
--- a/media/mtp/mtp.h
+++ b/media/mtp/mtp.h
@@ -22,8 +22,6 @@
#define MTP_STANDARD_VERSION 100
-#define MTP_FIRST_STORAGE_ID 0x00010001
-
// Container Types
#define MTP_CONTAINER_TYPE_UNDEFINED 0
#define MTP_CONTAINER_TYPE_COMMAND 1
@@ -393,6 +391,19 @@
#define MTP_OPERATION_SET_OBJECT_REFERENCES 0x9811
#define MTP_OPERATION_SKIP 0x9820
+// Android extensions for direct file IO
+
+// Same as GetPartialObject, but with 64 bit offset
+#define MTP_OPERATION_GET_PARTIAL_OBJECT_64 0x95C1
+// Same as GetPartialObject64, but copying host to device
+#define MTP_OPERATION_SEND_PARTIAL_OBJECT 0x95C2
+// Truncates file to 64 bit length
+#define MTP_OPERATION_TRUNCATE_OBJECT 0x95C3
+// Must be called before using SendPartialObject and TruncateObject
+#define MTP_OPERATION_BEGIN_EDIT_OBJECT 0x95C4
+// Called to commit changes made by SendPartialObject and TruncateObject
+#define MTP_OPERATION_END_EDIT_OBJECT 0x95C5
+
// MTP Response Codes
#define MTP_RESPONSE_UNDEFINED 0x2000
#define MTP_RESPONSE_OK 0x2001
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 8e18f2a..29c5741 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -35,7 +35,6 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.LocalPowerManager;
@@ -56,7 +55,6 @@
import com.android.internal.view.BaseInputHandler;
import com.android.internal.widget.PointerLocationView;
-import android.telephony.TelephonyManager;
import android.util.Config;
import android.util.EventLog;
import android.util.Log;
@@ -231,6 +229,7 @@
boolean mSafeMode;
WindowState mStatusBar = null;
boolean mStatusBarCanHide;
+ int mScreenMarginBottom;
final ArrayList<WindowState> mStatusBarPanels = new ArrayList<WindowState>();
WindowState mKeyguard = null;
KeyguardViewMediator mKeyguardMediator;
@@ -1032,6 +1031,14 @@
return STATUS_BAR_LAYER;
}
+ public int getNonDecorDisplayWidth(int fullWidth) {
+ return fullWidth;
+ }
+
+ public int getNonDecorDisplayHeight(int fullHeight) {
+ return fullHeight - mScreenMarginBottom;
+ }
+
public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs) {
return attrs.type == WindowManager.LayoutParams.TYPE_KEYGUARD;
}
@@ -1181,6 +1188,8 @@
// The Configuration will be stable by now, so we can load this
mStatusBarCanHide = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_statusBarCanHide);
+ mScreenMarginBottom = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.screen_margin_bottom);
break;
case TYPE_STATUS_BAR_PANEL:
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 19295e6d..f8a5cfb 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -156,6 +156,14 @@
return true;
}
+static void scalePointerCoords(const PointerCoords* inCoords, size_t count, float scaleFactor,
+ PointerCoords* outCoords) {
+ for (size_t i = 0; i < count; i++) {
+ outCoords[i] = inCoords[i];
+ outCoords[i].scale(scaleFactor);
+ }
+}
+
static void dumpRegion(String8& dump, const SkRegion& region) {
if (region.isEmpty()) {
dump.append("<empty>");
@@ -1494,6 +1502,7 @@
target.flags = targetFlags;
target.xOffset = - window->frameLeft;
target.yOffset = - window->frameTop;
+ target.scaleFactor = window->scaleFactor;
target.pointerIds = pointerIds;
}
@@ -1506,6 +1515,7 @@
target.flags = 0;
target.xOffset = 0;
target.yOffset = 0;
+ target.scaleFactor = 1.0f;
}
}
@@ -1607,12 +1617,12 @@
bool resumeWithAppendedMotionSample) {
#if DEBUG_DISPATCH_CYCLE
LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, "
- "xOffset=%f, yOffset=%f, "
+ "xOffset=%f, yOffset=%f, scaleFactor=%f"
"pointerIds=0x%x, "
"resumeWithAppendedMotionSample=%s",
connection->getInputChannelName(), inputTarget->flags,
inputTarget->xOffset, inputTarget->yOffset,
- inputTarget->pointerIds.value,
+ inputTarget->scaleFactor, inputTarget->pointerIds.value,
toString(resumeWithAppendedMotionSample));
#endif
@@ -1693,8 +1703,19 @@
// consumed the motion event (or if the channel is broken).
MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
MotionSample* appendedMotionSample = motionEntry->lastSample;
- status_t status = connection->inputPublisher.appendMotionSample(
- appendedMotionSample->eventTime, appendedMotionSample->pointerCoords);
+ status_t status;
+ if (motionEventDispatchEntry->scaleFactor == 1.0f) {
+ status = connection->inputPublisher.appendMotionSample(
+ appendedMotionSample->eventTime, appendedMotionSample->pointerCoords);
+ } else {
+ PointerCoords scaledCoords[MAX_POINTERS];
+ for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+ scaledCoords[i] = appendedMotionSample->pointerCoords[i];
+ scaledCoords[i].scale(motionEventDispatchEntry->scaleFactor);
+ }
+ status = connection->inputPublisher.appendMotionSample(
+ appendedMotionSample->eventTime, scaledCoords);
+ }
if (status == OK) {
#if DEBUG_BATCHING
LOGD("channel '%s' ~ Successfully streamed new motion sample.",
@@ -1731,7 +1752,8 @@
// This is a new event.
// Enqueue a new dispatch entry onto the outbound queue for this connection.
DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref
- inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset);
+ inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset,
+ inputTarget->scaleFactor);
if (dispatchEntry->hasForegroundTarget()) {
incrementPendingForegroundDispatchesLocked(eventEntry);
}
@@ -1827,24 +1849,34 @@
firstMotionSample = & motionEntry->firstSample;
}
+ PointerCoords scaledCoords[MAX_POINTERS];
+ const PointerCoords* usingCoords = firstMotionSample->pointerCoords;
+
// Set the X and Y offset depending on the input source.
- float xOffset, yOffset;
+ float xOffset, yOffset, scaleFactor;
if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
- xOffset = dispatchEntry->xOffset;
- yOffset = dispatchEntry->yOffset;
+ scaleFactor = dispatchEntry->scaleFactor;
+ xOffset = dispatchEntry->xOffset * scaleFactor;
+ yOffset = dispatchEntry->yOffset * scaleFactor;
+ if (scaleFactor != 1.0f) {
+ for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+ scaledCoords[i] = firstMotionSample->pointerCoords[i];
+ scaledCoords[i].scale(scaleFactor);
+ }
+ usingCoords = scaledCoords;
+ }
} else {
xOffset = 0.0f;
yOffset = 0.0f;
+ scaleFactor = 1.0f;
}
// Publish the motion event and the first motion sample.
status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState,
- xOffset, yOffset,
- motionEntry->xPrecision, motionEntry->yPrecision,
+ xOffset, yOffset, motionEntry->xPrecision, motionEntry->yPrecision,
motionEntry->downTime, firstMotionSample->eventTime,
- motionEntry->pointerCount, motionEntry->pointerIds,
- firstMotionSample->pointerCoords);
+ motionEntry->pointerCount, motionEntry->pointerIds, usingCoords);
if (status) {
LOGE("channel '%s' ~ Could not publish motion event, "
@@ -1857,7 +1889,7 @@
MotionSample* nextMotionSample = firstMotionSample->next;
for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
status = connection->inputPublisher.appendMotionSample(
- nextMotionSample->eventTime, nextMotionSample->pointerCoords);
+ nextMotionSample->eventTime, usingCoords);
if (status == NO_MEMORY) {
#if DEBUG_DISPATCH_CYCLE
LOGD("channel '%s' ~ Shared memory buffer full. Some motion samples will "
@@ -2095,18 +2127,21 @@
}
int32_t xOffset, yOffset;
+ float scaleFactor;
const InputWindow* window = getWindowLocked(connection->inputChannel);
if (window) {
xOffset = -window->frameLeft;
yOffset = -window->frameTop;
+ scaleFactor = window->scaleFactor;
} else {
xOffset = 0;
yOffset = 0;
+ scaleFactor = 1.0f;
}
DispatchEntry* cancelationDispatchEntry =
mAllocator.obtainDispatchEntry(cancelationEventEntry, // increments ref
- 0, xOffset, yOffset);
+ 0, xOffset, yOffset, scaleFactor);
connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
mAllocator.releaseEventEntry(cancelationEventEntry);
@@ -2957,7 +2992,7 @@
const InputWindow& window = mWindows[i];
dump.appendFormat(INDENT2 "%d: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
"visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
- "frame=[%d,%d][%d,%d], "
+ "frame=[%d,%d][%d,%d], scale=%f, "
"touchableRegion=",
i, window.name.string(),
toString(window.paused),
@@ -2968,7 +3003,8 @@
window.layoutParamsFlags, window.layoutParamsType,
window.layer,
window.frameLeft, window.frameTop,
- window.frameRight, window.frameBottom);
+ window.frameRight, window.frameBottom,
+ window.scaleFactor);
dumpRegion(dump, window.touchableRegion);
dump.appendFormat(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
window.ownerPid, window.ownerUid,
@@ -3460,13 +3496,14 @@
InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
EventEntry* eventEntry,
- int32_t targetFlags, float xOffset, float yOffset) {
+ int32_t targetFlags, float xOffset, float yOffset, float scaleFactor) {
DispatchEntry* entry = mDispatchEntryPool.alloc();
entry->eventEntry = eventEntry;
eventEntry->refCount += 1;
entry->targetFlags = targetFlags;
entry->xOffset = xOffset;
entry->yOffset = yOffset;
+ entry->scaleFactor = scaleFactor;
entry->inProgress = false;
entry->headMotionSample = NULL;
entry->tailMotionSample = NULL;
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 1e118c4..dd89328 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -112,6 +112,10 @@
// (ignored for KeyEvents)
float xOffset, yOffset;
+ // Scaling factor to apply to MotionEvent as it is delivered.
+ // (ignored for KeyEvents)
+ float scaleFactor;
+
// The subset of pointer ids to include in motion events dispatched to this input target
// if FLAG_SPLIT is set.
BitSet32 pointerIds;
@@ -431,6 +435,7 @@
int32_t targetFlags;
float xOffset;
float yOffset;
+ float scaleFactor;
// True if dispatch has started.
bool inProgress;
@@ -559,7 +564,7 @@
nsecs_t downTime, uint32_t pointerCount,
const int32_t* pointerIds, const PointerCoords* pointerCoords);
DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry,
- int32_t targetFlags, float xOffset, float yOffset);
+ int32_t targetFlags, float xOffset, float yOffset, float scaleFactor);
CommandEntry* obtainCommandEntry(Command command);
void releaseInjectionState(InjectionState* injectionState);
diff --git a/services/input/InputWindow.cpp b/services/input/InputWindow.cpp
index b552f6d..ccea9e4 100644
--- a/services/input/InputWindow.cpp
+++ b/services/input/InputWindow.cpp
@@ -29,8 +29,7 @@
}
bool InputWindow::frameContainsPoint(int32_t x, int32_t y) const {
- return x >= frameLeft && x <= frameRight
- && y >= frameTop && y <= frameBottom;
+ return x <= frameRight || y <= frameBottom;
}
bool InputWindow::isTrustedOverlay() const {
diff --git a/services/input/InputWindow.h b/services/input/InputWindow.h
index 9c43067..72f1773 100644
--- a/services/input/InputWindow.h
+++ b/services/input/InputWindow.h
@@ -131,6 +131,7 @@
int32_t frameTop;
int32_t frameRight;
int32_t frameBottom;
+ float scaleFactor;
SkRegion touchableRegion;
bool visible;
bool canReceiveKeys;
@@ -144,6 +145,11 @@
bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
bool frameContainsPoint(int32_t x, int32_t y) const;
+ /* These use the globalScale to convert a given screen offset to the
+ * corresponding location within the window.
+ */
+ int32_t displayToWindowX(int32_t x) const;
+
/* Returns true if the window is of a trusted type that is allowed to silently
* overlay other windows for the purpose of implementing the secure views feature.
* Trusted overlays, such as IME windows, can partly obscure other windows without causing
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 91ada6b..a100f1f 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -64,6 +64,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
@@ -144,7 +145,8 @@
private Context mContext;
private NativeDaemonConnector mConnector;
- private String mLegacyState = Environment.MEDIA_REMOVED;
+ private final HashMap<String, String> mVolumeStates = new HashMap<String, String>();
+ private String mExternalStoragePath;
private PackageManagerService mPms;
private boolean mUmsEnabling;
// Used as a lock for methods that register/unregister listeners.
@@ -442,29 +444,54 @@
* to make the media scanner run.
*/
if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
- notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, VolumeState.Mounted);
+ notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia,
+ VolumeState.Mounted);
return;
}
new Thread() {
@Override
public void run() {
try {
- String path = Environment.getExternalStorageDirectory().getPath();
- String state = getVolumeState(path);
-
- if (mEmulateExternalStorage) {
- notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Mounted);
- } else if (state.equals(Environment.MEDIA_UNMOUNTED)) {
- int rc = doMountVolume(path);
- if (rc != StorageResultCode.OperationSucceeded) {
- Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc));
+ // it is not safe to call vold with mVolumeStates locked
+ // so we make a copy of the paths and states and process them
+ // outside the lock
+ String[] paths, states;
+ int count;
+ synchronized (mVolumeStates) {
+ Set<String> keys = mVolumeStates.keySet();
+ count = keys.size();
+ paths = (String[])keys.toArray(new String[count]);
+ states = new String[count];
+ for (int i = 0; i < count; i++) {
+ states[i] = mVolumeStates.get(paths[i]);
}
- } else if (state.equals(Environment.MEDIA_SHARED)) {
- /*
- * Bootstrap UMS enabled state since vold indicates
- * the volume is shared (runtime restart while ums enabled)
- */
- notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Shared);
+ }
+
+ for (int i = 0; i < count; i++) {
+ String path = paths[i];
+ String state = states[i];
+
+ if (state.equals(Environment.MEDIA_UNMOUNTED)) {
+ int rc = doMountVolume(path);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Slog.e(TAG, String.format("Boot-time mount failed (%d)",
+ rc));
+ }
+ } else if (state.equals(Environment.MEDIA_SHARED)) {
+ /*
+ * Bootstrap UMS enabled state since vold indicates
+ * the volume is shared (runtime restart while ums enabled)
+ */
+ notifyVolumeStateChange(null, path, VolumeState.NoMedia,
+ VolumeState.Shared);
+ }
+ }
+
+ /* notify external storage has mounted to trigger media scanner */
+ if (mEmulateExternalStorage) {
+ notifyVolumeStateChange(null,
+ Environment.getExternalStorageDirectory().getPath(),
+ VolumeState.NoMedia, VolumeState.Mounted);
}
/*
@@ -516,35 +543,36 @@
}
private void updatePublicVolumeState(String path, String state) {
- if (!path.equals(Environment.getExternalStorageDirectory().getPath())) {
- Slog.w(TAG, "Multiple volumes not currently supported");
+ String oldState;
+ synchronized(mVolumeStates) {
+ oldState = mVolumeStates.put(path, state);
+ }
+ if (state.equals(oldState)) {
+ Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
+ state, state, path));
return;
}
- if (mLegacyState.equals(state)) {
- Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
- return;
- }
- // Update state on PackageManager, but only of real events
- if (!mEmulateExternalStorage) {
- if (Environment.MEDIA_UNMOUNTED.equals(state)) {
- mPms.updateExternalMediaStatus(false, false);
+ Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
- /*
- * Some OBBs might have been unmounted when this volume was
- * unmounted, so send a message to the handler to let it know to
- * remove those from the list of mounted OBBS.
- */
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE,
- path));
- } else if (Environment.MEDIA_MOUNTED.equals(state)) {
- mPms.updateExternalMediaStatus(true, false);
+ if (path.equals(mExternalStoragePath)) {
+ // Update state on PackageManager, but only of real events
+ if (!mEmulateExternalStorage) {
+ if (Environment.MEDIA_UNMOUNTED.equals(state)) {
+ mPms.updateExternalMediaStatus(false, false);
+
+ /*
+ * Some OBBs might have been unmounted when this volume was
+ * unmounted, so send a message to the handler to let it know to
+ * remove those from the list of mounted OBBS.
+ */
+ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
+ OBB_FLUSH_MOUNT_STATE, path));
+ } else if (Environment.MEDIA_MOUNTED.equals(state)) {
+ mPms.updateExternalMediaStatus(true, false);
+ }
}
}
-
- String oldState = mLegacyState;
- mLegacyState = state;
-
synchronized (mListeners) {
for (int i = mListeners.size() -1; i >= 0; i--) {
MountServiceBinderListener bl = mListeners.get(i);
@@ -575,20 +603,15 @@
/**
* Determine media state and UMS detection status
*/
- String path = Environment.getExternalStorageDirectory().getPath();
- String state = Environment.MEDIA_REMOVED;
-
try {
String[] vols = mConnector.doListCommand(
"volume list", VoldResponseCode.VolumeListResult);
for (String volstr : vols) {
String[] tok = volstr.split(" ");
// FMT: <label> <mountpoint> <state>
- if (!tok[1].equals(path)) {
- Slog.w(TAG, String.format(
- "Skipping unknown volume '%s'",tok[1]));
- continue;
- }
+ String path = tok[1];
+ String state = Environment.MEDIA_REMOVED;
+
int st = Integer.parseInt(tok[2]);
if (st == VolumeState.NoMedia) {
state = Environment.MEDIA_REMOVED;
@@ -603,14 +626,15 @@
} else {
throw new Exception(String.format("Unexpected state %d", st));
}
- }
- if (state != null) {
- if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
- updatePublicVolumeState(path, state);
+
+ if (state != null) {
+ if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
+ updatePublicVolumeState(path, state);
+ }
}
} catch (Exception e) {
Slog.e(TAG, "Error processing initial volume state", e);
- updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
+ updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED);
}
try {
@@ -1052,11 +1076,12 @@
public MountService(Context context) {
mContext = context;
+ mExternalStoragePath = Environment.getExternalStorageDirectory().getPath();
mEmulateExternalStorage = context.getResources().getBoolean(
com.android.internal.R.bool.config_emulateExternalStorage);
if (mEmulateExternalStorage) {
Slog.d(TAG, "using emulated external storage");
- mLegacyState = Environment.MEDIA_MOUNTED;
+ mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED);
}
// XXX: This will go away soon in favor of IMountServiceObserver
@@ -1125,54 +1150,56 @@
validatePermission(android.Manifest.permission.SHUTDOWN);
Slog.i(TAG, "Shutting down");
+ synchronized (mVolumeStates) {
+ for (String path : mVolumeStates.keySet()) {
+ String state = mVolumeStates.get(path);
- String path = Environment.getExternalStorageDirectory().getPath();
- String state = getVolumeState(path);
-
- if (state.equals(Environment.MEDIA_SHARED)) {
- /*
- * If the media is currently shared, unshare it.
- * XXX: This is still dangerous!. We should not
- * be rebooting at *all* if UMS is enabled, since
- * the UMS host could have dirty FAT cache entries
- * yet to flush.
- */
- setUsbMassStorageEnabled(false);
- } else if (state.equals(Environment.MEDIA_CHECKING)) {
- /*
- * If the media is being checked, then we need to wait for
- * it to complete before being able to proceed.
- */
- // XXX: @hackbod - Should we disable the ANR timer here?
- int retries = 30;
- while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException iex) {
- Slog.e(TAG, "Interrupted while waiting for media", iex);
- break;
+ if (state.equals(Environment.MEDIA_SHARED)) {
+ /*
+ * If the media is currently shared, unshare it.
+ * XXX: This is still dangerous!. We should not
+ * be rebooting at *all* if UMS is enabled, since
+ * the UMS host could have dirty FAT cache entries
+ * yet to flush.
+ */
+ setUsbMassStorageEnabled(false);
+ } else if (state.equals(Environment.MEDIA_CHECKING)) {
+ /*
+ * If the media is being checked, then we need to wait for
+ * it to complete before being able to proceed.
+ */
+ // XXX: @hackbod - Should we disable the ANR timer here?
+ int retries = 30;
+ while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException iex) {
+ Slog.e(TAG, "Interrupted while waiting for media", iex);
+ break;
+ }
+ state = Environment.getExternalStorageState();
+ }
+ if (retries == 0) {
+ Slog.e(TAG, "Timed out waiting for media to check");
+ }
}
- state = Environment.getExternalStorageState();
- }
- if (retries == 0) {
- Slog.e(TAG, "Timed out waiting for media to check");
- }
- }
- if (state.equals(Environment.MEDIA_MOUNTED)) {
- // Post a unmount message.
- ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
- mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
- } else if (observer != null) {
- /*
- * Observer is waiting for onShutDownComplete when we are done.
- * Since nothing will be done send notification directly so shutdown
- * sequence can continue.
- */
- try {
- observer.onShutDownComplete(StorageResultCode.OperationSucceeded);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException when shutting down");
+ if (state.equals(Environment.MEDIA_MOUNTED)) {
+ // Post a unmount message.
+ ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
+ mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
+ } else if (observer != null) {
+ /*
+ * Observer is waiting for onShutDownComplete when we are done.
+ * Since nothing will be done send notification directly so shutdown
+ * sequence can continue.
+ */
+ try {
+ observer.onShutDownComplete(StorageResultCode.OperationSucceeded);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException when shutting down");
+ }
+ }
}
}
}
@@ -1244,16 +1271,15 @@
* @return state of the volume at the specified mount point
*/
public String getVolumeState(String mountPoint) {
- /*
- * XXX: Until we have multiple volume discovery, just hardwire
- * this to /sdcard
- */
- if (!mountPoint.equals(Environment.getExternalStorageDirectory().getPath())) {
- Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
- throw new IllegalArgumentException();
- }
+ synchronized (mVolumeStates) {
+ String state = mVolumeStates.get(mountPoint);
+ if (state == null) {
+ Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
+ throw new IllegalArgumentException();
+ }
- return mLegacyState;
+ return state;
+ }
}
public boolean isExternalStorageEmulated() {
@@ -1727,6 +1753,18 @@
}
}
+ public String[] getVolumeList() {
+ synchronized(mVolumeStates) {
+ Set<String> volumes = mVolumeStates.keySet();
+ String[] result = new String[volumes.size()];
+ int i = 0;
+ for (String volume : volumes) {
+ result[i++] = volume;
+ }
+ return result;
+ }
+ }
+
private void addObbStateLocked(ObbState obbState) throws RemoteException {
final IBinder binder = obbState.getBinder();
List<ObbState> obbStates = mObbMounts.get(binder);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 54cc885..811221e 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -74,6 +74,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.Proxy;
@@ -146,7 +147,7 @@
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
static final String TAG = "ActivityManager";
static final boolean DEBUG = false;
- static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ static final boolean localLOGV = DEBUG;
static final boolean DEBUG_SWITCH = localLOGV || false;
static final boolean DEBUG_TASKS = localLOGV || false;
static final boolean DEBUG_PAUSE = localLOGV || false;
@@ -546,6 +547,12 @@
ProcessRecord mHomeProcess;
/**
+ * Packages that the user has asked to have run in screen size
+ * compatibility mode instead of filling the screen.
+ */
+ final HashSet<String> mScreenCompatPackages = new HashSet<String>();
+
+ /**
* Set of PendingResultRecord objects that are currently active.
*/
final HashSet mPendingResultRecords = new HashSet();
@@ -2091,6 +2098,74 @@
}
}
+ CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
+ return new CompatibilityInfo(ai, mConfiguration.screenLayout,
+ mScreenCompatPackages.contains(ai.packageName));
+ }
+
+ public void setPackageScreenCompatMode(String packageName, boolean compatEnabled) {
+ synchronized (this) {
+ ApplicationInfo ai = null;
+ try {
+ ai = AppGlobals.getPackageManager().
+ getApplicationInfo(packageName, STOCK_PM_FLAGS);
+ } catch (RemoteException e) {
+ }
+ if (ai == null) {
+ Slog.w(TAG, "setPackageScreenCompatMode failed: unknown package " + packageName);
+ return;
+ }
+ boolean changed = false;
+ if (compatEnabled) {
+ if (!mScreenCompatPackages.contains(packageName)) {
+ changed = true;
+ mScreenCompatPackages.add(packageName);
+ }
+ } else {
+ if (mScreenCompatPackages.contains(packageName)) {
+ changed = true;
+ mScreenCompatPackages.remove(packageName);
+ }
+ }
+ if (changed) {
+ CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai);
+
+ // Tell all processes that loaded this package about the change.
+ for (int i=mLruProcesses.size()-1; i>=0; i--) {
+ ProcessRecord app = mLruProcesses.get(i);
+ if (!app.pkgList.contains(packageName)) {
+ continue;
+ }
+ try {
+ if (app.thread != null) {
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
+ + app.processName + " new compat " + ci);
+ app.thread.updatePackageCompatibilityInfo(packageName, ci);
+ }
+ } catch (Exception e) {
+ }
+ }
+
+ // All activities that came from the packge must be
+ // restarted as if there was a config change.
+ for (int i=mMainStack.mHistory.size()-1; i>=0; i--) {
+ ActivityRecord a = (ActivityRecord)mMainStack.mHistory.get(i);
+ if (a.info.packageName.equals(packageName)) {
+ a.forceNewConfig = true;
+ }
+ }
+
+ ActivityRecord starting = mMainStack.topRunningActivityLocked(null);
+ if (starting != null) {
+ mMainStack.ensureActivityConfigurationLocked(starting, 0);
+ // And we need to make sure at this point that all other activities
+ // are made visible with the correct configuration.
+ mMainStack.ensureActivitiesVisibleLocked(starting, 0);
+ }
+ }
+ }
+ }
+
void reportResumedActivityLocked(ActivityRecord r) {
//Slog.i(TAG, "**** REPORT RESUME: " + r);
@@ -3589,12 +3664,14 @@
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Binding proc "
+ processName + " with config " + mConfiguration);
- thread.bindApplication(processName, app.instrumentationInfo != null
- ? app.instrumentationInfo : app.info, providers,
+ ApplicationInfo appInfo = app.instrumentationInfo != null
+ ? app.instrumentationInfo : app.info;
+ thread.bindApplication(processName, appInfo, providers,
app.instrumentationClass, app.instrumentationProfileFile,
app.instrumentationArguments, app.instrumentationWatcher, testMode,
isRestrictedBackupMode || !normalMode,
- mConfiguration, getCommonServicesLocked(),
+ mConfiguration, compatibilityInfoForPackageLocked(appInfo),
+ getCommonServicesLocked(),
mCoreSettingsObserver.getCoreSettingsLocked());
updateLruProcessLocked(app, false, true);
app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
@@ -3685,7 +3762,9 @@
if (DEBUG_BACKUP) Slog.v(TAG, "New app is backup target, launching agent for " + app);
ensurePackageDexOpt(mBackupTarget.appInfo.packageName);
try {
- thread.scheduleCreateBackupAgent(mBackupTarget.appInfo, mBackupTarget.backupMode);
+ thread.scheduleCreateBackupAgent(mBackupTarget.appInfo,
+ compatibilityInfoForPackageLocked(mBackupTarget.appInfo),
+ mBackupTarget.backupMode);
} catch (Exception e) {
Slog.w(TAG, "Exception scheduling backup agent creation: ");
e.printStackTrace();
@@ -7776,6 +7855,10 @@
}
pw.println(" mConfiguration: " + mConfiguration);
pw.println(" mConfigWillChange: " + mMainStack.mConfigWillChange);
+ if (mScreenCompatPackages.size() > 0) {
+ pw.print(" mScreenCompatPackages=");
+ pw.println(mScreenCompatPackages);
+ }
pw.println(" mSleeping=" + mSleeping + " mShuttingDown=" + mShuttingDown);
if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
|| mOrigWaitForDebugger) {
@@ -9238,7 +9321,8 @@
r.stats.startLaunchedLocked();
}
ensurePackageDexOpt(r.serviceInfo.packageName);
- app.thread.scheduleCreateService(r, r.serviceInfo);
+ app.thread.scheduleCreateService(r, r.serviceInfo,
+ compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo));
r.postNotification();
created = true;
} finally {
@@ -10342,7 +10426,8 @@
if (proc.thread != null) {
if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc already running: " + proc);
try {
- proc.thread.scheduleCreateBackupAgent(app, backupMode);
+ proc.thread.scheduleCreateBackupAgent(app,
+ compatibilityInfoForPackageLocked(app), backupMode);
} catch (RemoteException e) {
// Will time out on the backup manager side
}
@@ -10414,7 +10499,8 @@
// If the app crashed during backup, 'thread' will be null here
if (proc.thread != null) {
try {
- proc.thread.scheduleDestroyBackupAgent(appInfo);
+ proc.thread.scheduleDestroyBackupAgent(appInfo,
+ compatibilityInfoForPackageLocked(appInfo));
} catch (Exception e) {
Slog.e(TAG, "Exception when unbinding backup agent:");
e.printStackTrace();
@@ -11261,6 +11347,7 @@
+ ": " + r);
ensurePackageDexOpt(r.intent.getComponent().getPackageName());
app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
+ compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo),
r.resultCode, r.resultData, r.resultExtras, r.ordered);
if (DEBUG_BROADCAST) Slog.v(TAG,
"Process cur broadcast " + r + " DELIVERED for app " + app);
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 0fb30ff..cb0a0f0 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -111,6 +111,7 @@
boolean hasBeenLaunched;// has this activity ever been launched?
boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
boolean immersive; // immersive mode (don't interrupt if possible)
+ boolean forceNewConfig; // force re-create with new config next time
String stringName; // for caching of toString().
@@ -174,7 +175,8 @@
pw.print(" immersive="); pw.print(immersive);
pw.print(" launchMode="); pw.println(launchMode);
pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy);
- pw.print(" thumbnailNeeded="); pw.println(thumbnailNeeded);
+ pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded);
+ pw.print(" forceNewConfig="); pw.println(forceNewConfig);
if (launchTime != 0 || startTime != 0) {
pw.print(prefix); pw.print("launchTime=");
TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime=");
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index c087aecf..f3850422 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -537,9 +537,11 @@
}
mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
r.sleeping = false;
+ r.forceNewConfig = false;
app.thread.scheduleLaunchActivity(new Intent(r.intent), r,
System.identityHashCode(r),
- r.info, r.icicle, results, newIntents, !andResume,
+ r.info, mService.compatibilityInfoForPackageLocked(r.info.applicationInfo),
+ r.icicle, results, newIntents, !andResume,
mService.isNextTransitionForward());
if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
@@ -3750,7 +3752,7 @@
// Short circuit: if the two configurations are the exact same
// object (the common case), then there is nothing to do.
Configuration newConfig = mService.mConfiguration;
- if (r.configuration == newConfig) {
+ if (r.configuration == newConfig && !r.forceNewConfig) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Configuration unchanged in " + r);
return true;
@@ -3775,6 +3777,7 @@
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Configuration doesn't matter not running " + r);
r.stopFreezingScreenLocked(false);
+ r.forceNewConfig = false;
return true;
}
@@ -3786,10 +3789,11 @@
+ Integer.toHexString(r.info.configChanges)
+ ", newConfig=" + newConfig);
}
- if ((changes&(~r.info.configChanges)) != 0) {
+ if ((changes&(~r.info.configChanges)) != 0 || r.forceNewConfig) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
r.configChangeFlags |= changes;
r.startFreezingScreenLocked(r.app, globalChanges);
+ r.forceNewConfig = false;
if (r.app == null || r.app.thread == null) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
"Switch is destroying non-running " + r);
@@ -3860,6 +3864,7 @@
try {
if (DEBUG_SWITCH) Slog.i(TAG, "Switch is restarting resumed " + r);
+ r.forceNewConfig = false;
r.app.thread.scheduleRelaunchActivity(r, results, newIntents,
changes, !andResume, mService.mConfiguration);
// Note: don't need to call pauseIfSleepingLocked() here, because
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 353ff6d..a63ffae 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -24,6 +24,7 @@
import android.app.IInstrumentationWatcher;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
+import android.content.res.CompatibilityInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java
index 45a78af..57f0799 100644
--- a/services/java/com/android/server/wm/InputMonitor.java
+++ b/services/java/com/android/server/wm/InputMonitor.java
@@ -205,12 +205,21 @@
inputWindow.ownerPid = child.mSession.mPid;
inputWindow.ownerUid = child.mSession.mUid;
- final Rect frame = child.mFrame;
+ final Rect frame = child.mScaledFrame;
inputWindow.frameLeft = frame.left;
inputWindow.frameTop = frame.top;
inputWindow.frameRight = frame.right;
inputWindow.frameBottom = frame.bottom;
+ if (child.mGlobalScale != 1) {
+ // If we are scaling the window, input coordinates need
+ // to be inversely scaled to map from what is on screen
+ // to what is actually being touched in the UI.
+ inputWindow.scaleFactor = 1.0f/child.mGlobalScale;
+ } else {
+ inputWindow.scaleFactor = 1;
+ }
+
child.getTouchableRegion(inputWindow.touchableRegion);
}
diff --git a/services/java/com/android/server/wm/InputWindow.java b/services/java/com/android/server/wm/InputWindow.java
index e3eb473..578120e 100644
--- a/services/java/com/android/server/wm/InputWindow.java
+++ b/services/java/com/android/server/wm/InputWindow.java
@@ -46,6 +46,10 @@
public int frameRight;
public int frameBottom;
+ // Global scaling factor applied to touch events when they are dispatched
+ // to the window
+ public float scaleFactor;
+
// Window touchable region.
public final Region touchableRegion = new Region();
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 33e6a36..769e423 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -19,7 +19,6 @@
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
-import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -586,6 +585,7 @@
// The frame use to limit the size of the app running in compatibility mode.
Rect mCompatibleScreenFrame = new Rect();
+ float mCompatibleScreenScale;
// The surface used to fill the outer rim of the app running in compatibility mode.
Surface mBackgroundFillerSurface = null;
WindowState mBackgroundFillerTarget = null;
@@ -1757,7 +1757,7 @@
boolean rawChanged = false;
float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : 0.5f;
float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
- int availw = wallpaperWin.mFrame.right-wallpaperWin.mFrame.left-dw;
+ int availw = wallpaperWin.mScaledFrame.right-wallpaperWin.mScaledFrame.left-dw;
int offset = availw > 0 ? -(int)(availw*wpx+.5f) : 0;
changed = wallpaperWin.mXOffset != offset;
if (changed) {
@@ -2887,14 +2887,14 @@
}
private boolean applyAnimationLocked(AppWindowToken wtoken,
- WindowManager.LayoutParams lp, int transit, boolean enter) {
+ WindowManager.LayoutParams lp, int transit, boolean enter, boolean bgFiller) {
// Only apply an animation if the display isn't frozen. If it is
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
// is running.
if (!mDisplayFrozen && mPolicy.isScreenOn()) {
Animation a;
- if (lp != null && (lp.flags & FLAG_COMPATIBLE_WINDOW) != 0) {
+ if (bgFiller) {
a = new FadeInOutAnimation(enter);
if (DEBUG_ANIM) Slog.v(TAG,
"applying FadeInOutAnimation for a window in compatibility mode");
@@ -3680,7 +3680,7 @@
}
boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
- boolean visible, int transit, boolean performLayout) {
+ boolean visible, int transit, boolean performLayout, boolean bgFiller) {
boolean delayed = false;
if (wtoken.clientHidden == visible) {
@@ -3702,7 +3702,7 @@
if (wtoken.animation == sDummyAnimation) {
wtoken.animation = null;
}
- applyAnimationLocked(wtoken, lp, transit, visible);
+ applyAnimationLocked(wtoken, lp, transit, visible, bgFiller);
changed = true;
if (wtoken.animation != null) {
delayed = runningAppAnimation = true;
@@ -3855,7 +3855,8 @@
}
final long origId = Binder.clearCallingIdentity();
- setTokenVisibilityLocked(wtoken, null, visible, WindowManagerPolicy.TRANSIT_UNSET, true);
+ setTokenVisibilityLocked(wtoken, null, visible, WindowManagerPolicy.TRANSIT_UNSET,
+ true, false);
wtoken.updateReportedVisibilityLocked();
Binder.restoreCallingIdentity(origId);
}
@@ -3981,7 +3982,8 @@
WindowToken basewtoken = mTokenMap.remove(token);
if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken);
- delayed = setTokenVisibilityLocked(wtoken, null, false, WindowManagerPolicy.TRANSIT_UNSET, true);
+ delayed = setTokenVisibilityLocked(wtoken, null, false,
+ WindowManagerPolicy.TRANSIT_UNSET, true, false);
wtoken.inPendingTransaction = false;
mOpeningApps.remove(wtoken);
wtoken.waitingToShow = false;
@@ -4753,8 +4755,8 @@
synchronized(mWindowMap) {
long ident = Binder.clearCallingIdentity();
- dw = mDisplay.getWidth();
- dh = mDisplay.getHeight();
+ dw = mPolicy.getNonDecorDisplayWidth(mDisplay.getWidth());
+ dh = mPolicy.getNonDecorDisplayHeight(mDisplay.getHeight());
int aboveAppLayer = mPolicy.windowTypeToLayerLw(
WindowManager.LayoutParams.TYPE_APPLICATION) * TYPE_LAYER_MULTIPLIER
@@ -4802,7 +4804,7 @@
// Don't include wallpaper in bounds calculation
if (!ws.mIsWallpaper) {
- final Rect wf = ws.mFrame;
+ final Rect wf = ws.mScaledFrame;
final Rect cr = ws.mContentInsets;
int left = wf.left + cr.left;
int top = wf.top + cr.top;
@@ -5447,7 +5449,10 @@
DisplayMetrics dm = new DisplayMetrics();
mDisplay.getMetrics(dm);
- CompatibilityInfo.updateCompatibleScreenFrame(dm, orientation, mCompatibleScreenFrame);
+ dm.realWidthPixels = mPolicy.getNonDecorDisplayWidth(dm.realWidthPixels);
+ dm.realHeightPixels = mPolicy.getNonDecorDisplayHeight(dm.realHeightPixels);
+ mCompatibleScreenScale = CompatibilityInfo.updateCompatibleScreenFrame(
+ dm, mCompatibleScreenFrame, null);
if (mScreenLayout == Configuration.SCREENLAYOUT_SIZE_UNDEFINED) {
// Note we only do this once because at this point we don't
@@ -6582,6 +6587,9 @@
final int dw = mDisplay.getWidth();
final int dh = mDisplay.getHeight();
+ final int innerDw = mPolicy.getNonDecorDisplayWidth(dw);
+ final int innerDh = mPolicy.getNonDecorDisplayHeight(dh);
+
final int N = mWindows.size();
int i;
@@ -6634,7 +6642,9 @@
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
win.mContentChanged = false;
}
+ win.prelayout();
mPolicy.layoutWindowLw(win, win.mAttrs, null);
+ win.evalNeedsBackgroundFiller(innerDw, innerDh);
win.mLayoutSeq = seq;
if (DEBUG_LAYOUT) Slog.v(TAG, "-> mFrame="
+ win.mFrame + " mContainingFrame="
@@ -6669,7 +6679,9 @@
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
win.mContentChanged = false;
}
+ win.prelayout();
mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow);
+ win.evalNeedsBackgroundFiller(innerDw, innerDh);
win.mLayoutSeq = seq;
if (DEBUG_LAYOUT) Slog.v(TAG, "-> mFrame="
+ win.mFrame + " mContainingFrame="
@@ -6700,6 +6712,9 @@
final int dw = mDisplay.getWidth();
final int dh = mDisplay.getHeight();
+ final int innerDw = mPolicy.getNonDecorDisplayWidth(dw);
+ final int innerDh = mPolicy.getNonDecorDisplayHeight(dh);
+
int i;
if (mFocusMayChange) {
@@ -6799,13 +6814,15 @@
boolean tokensAnimating = false;
final int NAT = mAppTokens.size();
for (i=0; i<NAT; i++) {
- if (mAppTokens.get(i).stepAnimationLocked(currentTime, dw, dh)) {
+ if (mAppTokens.get(i).stepAnimationLocked(currentTime,
+ innerDw, innerDh)) {
tokensAnimating = true;
}
}
final int NEAT = mExitingAppTokens.size();
for (i=0; i<NEAT; i++) {
- if (mExitingAppTokens.get(i).stepAnimationLocked(currentTime, dw, dh)) {
+ if (mExitingAppTokens.get(i).stepAnimationLocked(currentTime,
+ innerDw, innerDh)) {
tokensAnimating = true;
}
}
@@ -6858,8 +6875,8 @@
final boolean wasAnimating = w.mAnimating;
- int animDw = dw;
- int animDh = dh;
+ int animDw = innerDw;
+ int animDh = innerDh;
// If the window has moved due to its containing
// content frame changing, then we'd like to animate
@@ -7116,6 +7133,7 @@
LayoutParams animLp = null;
int bestAnimLayer = -1;
boolean fullscreenAnim = false;
+ boolean needBgFiller = false;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New wallpaper target=" + mWallpaperTarget
@@ -7155,9 +7173,10 @@
if (ws != null) {
// If this is a compatibility mode
// window, we will always use its anim.
- if ((ws.mAttrs.flags&FLAG_COMPATIBLE_WINDOW) != 0) {
+ if (ws.mNeedsBackgroundFiller) {
animLp = ws.mAttrs;
bestAnimLayer = Integer.MAX_VALUE;
+ needBgFiller = true;
} else if (!fullscreenAnim || ws.mLayer > bestAnimLayer) {
animLp = ws.mAttrs;
bestAnimLayer = ws.mLayer;
@@ -7222,7 +7241,8 @@
wtoken.reportedVisible = false;
wtoken.inPendingTransaction = false;
wtoken.animation = null;
- setTokenVisibilityLocked(wtoken, animLp, true, transit, false);
+ setTokenVisibilityLocked(wtoken, animLp, true,
+ transit, false, needBgFiller);
wtoken.updateReportedVisibilityLocked();
wtoken.waitingToShow = false;
wtoken.showAllWindowsLocked();
@@ -7234,7 +7254,8 @@
"Now closing app" + wtoken);
wtoken.inPendingTransaction = false;
wtoken.animation = null;
- setTokenVisibilityLocked(wtoken, animLp, false, transit, false);
+ setTokenVisibilityLocked(wtoken, animLp, false,
+ transit, false, needBgFiller);
wtoken.updateReportedVisibilityLocked();
wtoken.waitingToHide = false;
// Force the allDrawn flag, because we want to start
@@ -7737,12 +7758,14 @@
}
boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
- if (opaqueDrawn && w.isFullscreen(dw, dh)) {
+ if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
// This window completely covers everything behind it,
// so we want to leave all of them as unblurred (for
// performance reasons).
obscured = true;
- } else if (w.needsBackgroundFiller(dw, dh) && (canBeSeen || w.isAnimating())) {
+ } else if (w.mNeedsBackgroundFiller && w.mHasDrawn
+ && w.mViewVisibility == View.VISIBLE
+ && (canBeSeen || w.isAnimating())) {
// This window is in compatibility mode, and needs background filler.
obscured = true;
mBackgroundFillerTarget = w;
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index f8ff5f8..72049ec 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -72,6 +72,7 @@
final boolean mIsImWindow;
final boolean mIsWallpaper;
final boolean mIsFloatingLayer;
+ final boolean mEnforceSizeCompat;
int mViewVisibility;
boolean mPolicyVisibility = true;
boolean mPolicyVisibilityAfterAnim = true;
@@ -91,6 +92,7 @@
int mLastLayer;
boolean mHaveFrame;
boolean mObscured;
+ boolean mNeedsBackgroundFiller;
boolean mTurnOnScreen;
int mLayoutSeq = -1;
@@ -154,6 +156,7 @@
// Current transformation being applied.
boolean mHaveMatrix;
+ float mGlobalScale=1;
float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1;
float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1;
float mHScale=1, mVScale=1;
@@ -163,6 +166,7 @@
// "Real" frame that the application sees.
final Rect mFrame = new Rect();
final Rect mLastFrame = new Rect();
+ final Rect mScaledFrame = new Rect();
final Rect mContainingFrame = new Rect();
final Rect mDisplayFrame = new Rect();
@@ -273,6 +277,7 @@
mViewVisibility = viewVisibility;
DeathRecipient deathRecipient = new DeathRecipient();
mAlpha = a.alpha;
+ mEnforceSizeCompat = (mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0;
if (WindowManagerService.localLOGV) Slog.v(
WindowManagerService.TAG, "Window " + this + " client=" + c.asBinder()
+ " token=" + token + " (" + mAttrs.token + ")");
@@ -368,7 +373,7 @@
final Rect display = mDisplayFrame;
display.set(df);
- if ((mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0) {
+ if (mEnforceSizeCompat) {
container.intersect(mService.mCompatibleScreenFrame);
if ((mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) == 0) {
display.intersect(mService.mCompatibleScreenFrame);
@@ -416,6 +421,28 @@
// Now make sure the window fits in the overall display.
Gravity.applyDisplay(mAttrs.gravity, df, frame);
+ int adjRight=0, adjBottom=0;
+
+ if (mEnforceSizeCompat) {
+ // Adjust window offsets by the scaling factor.
+ int xoff = (int)((frame.left-mService.mCompatibleScreenFrame.left)*mGlobalScale)
+ - (frame.left-mService.mCompatibleScreenFrame.left);
+ int yoff = (int)((frame.top-mService.mCompatibleScreenFrame.top)*mGlobalScale)
+ - (frame.top-mService.mCompatibleScreenFrame.top);
+ frame.offset(xoff, yoff);
+
+ // We are temporarily going to apply the compatibility scale
+ // to the window so that we can correctly associate it with the
+ // content and visible frame.
+ adjRight = frame.right - frame.left;
+ adjRight = (int)((adjRight)*mGlobalScale + .5f) - adjRight;
+ adjBottom = frame.bottom - frame.top;
+ adjBottom = (int)((adjBottom)*mGlobalScale + .5f) - adjBottom;
+ frame.right += adjRight;
+ frame.bottom += adjBottom;
+ }
+ mScaledFrame.set(frame);
+
// Make sure the content and visible frames are inside of the
// final window frame.
if (content.left < frame.left) content.left = frame.left;
@@ -439,6 +466,22 @@
visibleInsets.right = frame.right-visible.right;
visibleInsets.bottom = frame.bottom-visible.bottom;
+ if (mEnforceSizeCompat) {
+ // Scale the computed insets back to the window's compatibility
+ // coordinate space, and put frame back to correct size.
+ final float invScale = 1.0f/mGlobalScale;
+ contentInsets.left = (int)(contentInsets.left*invScale);
+ contentInsets.top = (int)(contentInsets.top*invScale);
+ contentInsets.right = (int)(contentInsets.right*invScale);
+ contentInsets.bottom = (int)(contentInsets.bottom*invScale);
+ visibleInsets.left = (int)(visibleInsets.left*invScale);
+ visibleInsets.top = (int)(visibleInsets.top*invScale);
+ visibleInsets.right = (int)(visibleInsets.right*invScale);
+ visibleInsets.bottom = (int)(visibleInsets.bottom*invScale);
+ frame.right -= adjRight;
+ frame.bottom -= adjBottom;
+ }
+
if (mIsWallpaper && (fw != frame.width() || fh != frame.height())) {
mService.updateWallpaperOffsetLocked(this, mService.mDisplay.getWidth(),
mService.mDisplay.getHeight(), false);
@@ -819,9 +862,10 @@
if (!mLocalAnimating) {
if (WindowManagerService.DEBUG_ANIM) Slog.v(
WindowManagerService.TAG, "Starting animation in " + this +
- " @ " + currentTime + ": ww=" + mFrame.width() + " wh=" + mFrame.height() +
+ " @ " + currentTime + ": ww=" + mScaledFrame.width() +
+ " wh=" + mScaledFrame.height() +
" dw=" + dw + " dh=" + dh + " scale=" + mService.mWindowAnimationScale);
- mAnimation.initialize(mFrame.width(), mFrame.height(), dw, dh);
+ mAnimation.initialize(mScaledFrame.width(), mScaledFrame.height(), dw, dh);
mAnimation.setStartTime(currentTime);
mLocalAnimating = true;
mAnimating = true;
@@ -988,6 +1032,14 @@
return true;
}
+ void prelayout() {
+ if (mEnforceSizeCompat) {
+ mGlobalScale = mService.mCompatibleScreenScale;
+ } else {
+ mGlobalScale = 1;
+ }
+ }
+
void computeShownFrameLocked() {
final boolean selfTransformation = mHasLocalTransformation;
Transformation attachedTransformation =
@@ -1031,6 +1083,7 @@
// Compute the desired transformation.
tmpMatrix.setTranslate(0, 0);
+ tmpMatrix.postScale(mGlobalScale, mGlobalScale);
if (selfTransformation) {
tmpMatrix.postConcat(mTransformation.getMatrix());
}
@@ -1105,10 +1158,10 @@
}
mShownAlpha = mAlpha;
mHaveMatrix = false;
- mDsDx = 1;
+ mDsDx = mGlobalScale;
mDtDx = 0;
mDsDy = 0;
- mDtDy = 1;
+ mDtDy = mGlobalScale;
}
/**
@@ -1281,12 +1334,14 @@
&& mService.mPolicy.isScreenOn();
}
- boolean needsBackgroundFiller(int screenWidth, int screenHeight) {
- return
+ void evalNeedsBackgroundFiller(int screenWidth, int screenHeight) {
+ mNeedsBackgroundFiller =
// only if the application is requesting compatible window
- (mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0 &&
+ mEnforceSizeCompat &&
// only if it's visible
mHasDrawn && mViewVisibility == View.VISIBLE &&
+ // not needed if the compat window is actually full screen
+ !isFullscreenIgnoringCompat(screenWidth, screenHeight) &&
// and only if the application fills the compatible screen
mFrame.left <= mService.mCompatibleScreenFrame.left &&
mFrame.top <= mService.mCompatibleScreenFrame.top &&
@@ -1295,8 +1350,19 @@
}
boolean isFullscreen(int screenWidth, int screenHeight) {
- return mFrame.left <= 0 && mFrame.top <= 0 &&
- mFrame.right >= screenWidth && mFrame.bottom >= screenHeight;
+ if (mEnforceSizeCompat) {
+ return mFrame.left <= mService.mCompatibleScreenFrame.left &&
+ mFrame.top <= mService.mCompatibleScreenFrame.top &&
+ mFrame.right >= mService.mCompatibleScreenFrame.right &&
+ mFrame.bottom >= mService.mCompatibleScreenFrame.bottom;
+ } else {
+ return isFullscreenIgnoringCompat(screenWidth, screenHeight);
+ }
+ }
+
+ boolean isFullscreenIgnoringCompat(int screenWidth, int screenHeight) {
+ return mScaledFrame.left <= 0 && mScaledFrame.top <= 0 &&
+ mScaledFrame.right >= screenWidth && mScaledFrame.bottom >= screenHeight;
}
void removeLocked() {
@@ -1426,30 +1492,38 @@
return true;
}
+ private static void applyScaledInsets(Region outRegion, Rect frame, Rect inset, float scale) {
+ if (scale != 1) {
+ outRegion.set(frame.left + (int)(inset.left*scale),
+ frame.top + (int)(inset.top*scale),
+ frame.right - (int)(inset.right*scale),
+ frame.bottom - (int)(inset.bottom*scale));
+ } else {
+ outRegion.set(
+ frame.left + inset.left, frame.top + inset.top,
+ frame.right - inset.right, frame.bottom - inset.bottom);
+ }
+ }
+
public void getTouchableRegion(Region outRegion) {
- final Rect frame = mFrame;
+ final Rect frame = mScaledFrame;
switch (mTouchableInsets) {
default:
case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
outRegion.set(frame);
break;
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: {
- final Rect inset = mGivenContentInsets;
- outRegion.set(
- frame.left + inset.left, frame.top + inset.top,
- frame.right - inset.right, frame.bottom - inset.bottom);
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+ applyScaledInsets(outRegion, frame, mGivenContentInsets, mGlobalScale);
break;
- }
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: {
- final Rect inset = mGivenVisibleInsets;
- outRegion.set(
- frame.left + inset.left, frame.top + inset.top,
- frame.right - inset.right, frame.bottom - inset.bottom);
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+ applyScaledInsets(outRegion, frame, mGivenVisibleInsets, mGlobalScale);
break;
- }
case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: {
final Region givenTouchableRegion = mGivenTouchableRegion;
outRegion.set(givenTouchableRegion);
+ if (mGlobalScale != 1) {
+ outRegion.scale(mGlobalScale);
+ }
outRegion.translate(frame.left, frame.top);
break;
}
@@ -1512,7 +1586,8 @@
}
pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
pw.print(" h="); pw.print(mRequestedHeight);
- pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
+ pw.print(" mLayoutSeq="); pw.print(mLayoutSeq);
+ pw.print(" mNeedsBackgroundFiller="); pw.println(mNeedsBackgroundFiller);
if (mXOffset != 0 || mYOffset != 0) {
pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset);
pw.print(" y="); pw.println(mYOffset);
@@ -1533,6 +1608,7 @@
pw.println();
pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw);
pw.print(" last="); mLastFrame.printShortString(pw);
+ pw.print(" scaled="); mScaledFrame.printShortString(pw);
pw.println();
pw.print(prefix); pw.print("mContainingFrame=");
mContainingFrame.printShortString(pw);
@@ -1568,8 +1644,9 @@
pw.print(" mAlpha="); pw.print(mAlpha);
pw.print(" mLastAlpha="); pw.println(mLastAlpha);
}
- if (mHaveMatrix) {
- pw.print(prefix); pw.print("mDsDx="); pw.print(mDsDx);
+ if (mHaveMatrix || mGlobalScale != 1) {
+ pw.print(prefix); pw.print("mGlobalScale="); pw.print(mGlobalScale);
+ pw.print(" mDsDx="); pw.print(mDsDx);
pw.print(" mDtDx="); pw.print(mDtDx);
pw.print(" mDsDy="); pw.print(mDsDy);
pw.print(" mDtDy="); pw.println(mDtDy);
diff --git a/services/jni/com_android_server_InputWindow.cpp b/services/jni/com_android_server_InputWindow.cpp
index 8548b47..d36c010 100644
--- a/services/jni/com_android_server_InputWindow.cpp
+++ b/services/jni/com_android_server_InputWindow.cpp
@@ -40,6 +40,7 @@
jfieldID frameTop;
jfieldID frameRight;
jfieldID frameBottom;
+ jfieldID scaleFactor;
jfieldID touchableRegion;
jfieldID visible;
jfieldID canReceiveKeys;
@@ -102,6 +103,8 @@
gInputWindowClassInfo.frameRight);
outInputWindow->frameBottom = env->GetIntField(inputWindowObj,
gInputWindowClassInfo.frameBottom);
+ outInputWindow->scaleFactor = env->GetFloatField(inputWindowObj,
+ gInputWindowClassInfo.scaleFactor);
jobject regionObj = env->GetObjectField(inputWindowObj,
gInputWindowClassInfo.touchableRegion);
@@ -176,6 +179,9 @@
GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz,
"frameBottom", "I");
+ GET_FIELD_ID(gInputWindowClassInfo.scaleFactor, gInputWindowClassInfo.clazz,
+ "scaleFactor", "F");
+
GET_FIELD_ID(gInputWindowClassInfo.touchableRegion, gInputWindowClassInfo.clazz,
"touchableRegion", "Landroid/graphics/Region;");
diff --git a/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java b/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java
index 6192a3c..2a40c57 100644
--- a/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java
+++ b/tests/DpiTest/src/com/google/android/test/dpi/DpiTestActivity.java
@@ -62,7 +62,8 @@
| ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
| ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS
| ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
- app.getResources().setCompatibilityInfo(new CompatibilityInfo(ai));
+ app.getResources().setCompatibilityInfo(new CompatibilityInfo(ai,
+ getResources().getConfiguration().screenLayout, false));
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("ouch", e);