diff options
511 files changed, 13870 insertions, 4749 deletions
diff --git a/Android.mk b/Android.mk index 54ca200a5b84..3473e182a638 100644 --- a/Android.mk +++ b/Android.mk @@ -251,17 +251,22 @@ LOCAL_SRC_FILES += \ media/java/android/media/IAudioService.aidl \ media/java/android/media/IAudioFocusDispatcher.aidl \ media/java/android/media/IAudioRoutesObserver.aidl \ + media/java/android/media/IMediaRouterClient.aidl \ + media/java/android/media/IMediaRouterService.aidl \ media/java/android/media/IMediaScannerListener.aidl \ media/java/android/media/IMediaScannerService.aidl \ media/java/android/media/IRemoteControlClient.aidl \ media/java/android/media/IRemoteControlDisplay.aidl \ + media/java/android/media/IRemoteDisplayCallback.aidl \ + media/java/android/media/IRemoteDisplayProvider.aidl \ media/java/android/media/IRemoteVolumeObserver.aidl \ media/java/android/media/IRingtonePlayer.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ telephony/java/com/android/internal/telephony/ITelephony.aidl \ - telephony/java/com/android/internal/telephony/ISms.aidl \ + telephony/java/com/android/internal/telephony/ITelephonyListener.aidl \ telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \ + telephony/java/com/android/internal/telephony/ISms.aidl \ telephony/java/com/android/internal/telephony/IWapPushManager.aidl \ wifi/java/android/net/wifi/IWifiManager.aidl \ wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ @@ -597,6 +602,32 @@ $(full_target): $(framework_built) $(gen) $(INTERNAL_PLATFORM_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE)) +# ==== the private api stubs =================================== +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:=$(framework_docs_LOCAL_API_CHECK_SRC_FILES) +LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES) +LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES) +LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS) +LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH) +LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR) +LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR) +LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES) + +LOCAL_MODULE := private-api-stubs + +LOCAL_DROIDDOC_OPTIONS:=\ + $(framework_docs_LOCAL_DROIDDOC_OPTIONS) \ + -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android_private_stubs_current_intermediates/src \ + -showAnnotation android.annotation.PrivateApi \ + -nodocs + +LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk + +LOCAL_UNINSTALLABLE_MODULE := true + +include $(BUILD_DROIDDOC) + # ==== check javadoc comments but don't generate docs ======== include $(CLEAR_VARS) diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index c18f54259678..0344d261fd35 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -107,7 +107,7 @@ public class Am extends BaseCommand { " am switch-user <USER_ID>\n" + " am stop-user <USER_ID>\n" + " am stack create <TASK_ID> <RELATIVE_STACK_BOX_ID> <POSITION> <WEIGHT>\n" + - " am stack movetask <STACK_ID> <TASK_ID> [true|false]\n" + + " am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" + " am stack resize <STACK_ID> <WEIGHT>\n" + " am stack boxes\n" + " am stack box <STACK_BOX_ID>\n" + diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 2666b4172a08..db3d8bbc9cfc 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -187,6 +187,12 @@ public final class Bmgr { } private void doWipe() { + String transport = nextArg(); + if (transport == null) { + showUsage(); + return; + } + String pkg = nextArg(); if (pkg == null) { showUsage(); @@ -194,8 +200,8 @@ public final class Bmgr { } try { - mBmgr.clearBackupData(pkg); - System.out.println("Wiped backup data for " + pkg); + mBmgr.clearBackupData(transport, pkg); + System.out.println("Wiped backup data for " + pkg + " on " + transport); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -446,7 +452,7 @@ public final class Bmgr { System.err.println(" bmgr restore TOKEN PACKAGE..."); System.err.println(" bmgr restore PACKAGE"); System.err.println(" bmgr run"); - System.err.println(" bmgr wipe PACKAGE"); + System.err.println(" bmgr wipe TRANSPORT PACKAGE"); System.err.println(""); System.err.println("The 'backup' command schedules a backup pass for the named package."); System.err.println("Note that the backup pass will effectively be a no-op if the package"); @@ -462,8 +468,8 @@ public final class Bmgr { System.err.println(""); System.err.println("The 'list transports' command reports the names of the backup transports"); System.err.println("currently available on the device. These names can be passed as arguments"); - System.err.println("to the 'transport' command. The currently selected transport is indicated"); - System.err.println("with a '*' character."); + System.err.println("to the 'transport' and 'wipe' commands. The currently selected transport"); + System.err.println("is indicated with a '*' character."); System.err.println(""); System.err.println("The 'list sets' command reports the token and name of each restore set"); System.err.println("available to the device via the current transport."); @@ -491,7 +497,8 @@ public final class Bmgr { System.err.println("data changes."); System.err.println(""); System.err.println("The 'wipe' command causes all backed-up data for the given package to be"); - System.err.println("erased from the current transport's storage. The next backup operation"); + System.err.println("erased from the given transport's storage. The next backup operation"); System.err.println("that the given application performs will rewrite its entire data set."); + System.err.println("Transport names to use here are those reported by 'list transports'."); } } diff --git a/core/java/android/annotation/PrivateApi.java b/core/java/android/annotation/PrivateApi.java new file mode 100644 index 000000000000..985eafec5d33 --- /dev/null +++ b/core/java/android/annotation/PrivateApi.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008 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.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Indicates an API is exposed for use by bundled applications. + * <p> + * These APIs are not guaranteed to remain consistent release-to-release, + * and are not for use by apps linking against the SDK. + * @hide + */ +@Retention(RetentionPolicy.SOURCE) +public @interface PrivateApi { +} diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 193724dd3dfc..d6db8c2a0d13 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4857,7 +4857,11 @@ public class Activity extends ContextThemeWrapper mFragments.dump(prefix, fd, writer, args); - getWindow().getDecorView().getViewRootImpl().dump(prefix, fd, writer, args); + if (getWindow() != null && + getWindow().peekDecorView() != null && + getWindow().peekDecorView().getViewRootImpl() != null) { + getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args); + } mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index bb04063905d5..7ca345993c23 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2272,9 +2272,12 @@ public class ActivityManager { public static void dumpPackageStateStatic(FileDescriptor fd, String packageName) { FileOutputStream fout = new FileOutputStream(fd); PrintWriter pw = new FastPrintWriter(fout); - dumpService(pw, fd, Context.ACTIVITY_SERVICE, new String[] { "package", packageName }); + dumpService(pw, fd, Context.ACTIVITY_SERVICE, new String[] { + "-a", "package", packageName }); pw.println(); - dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName }); + dumpService(pw, fd, "meminfo", new String[] { "--local", packageName }); + pw.println(); + dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { "-a", packageName }); pw.println(); dumpService(pw, fd, "usagestats", new String[] { "--packages", packageName }); pw.println(); @@ -2296,7 +2299,7 @@ public class ActivityManager { pw.flush(); tp = new TransferPipe(); tp.setBufferPrefix(" "); - service.dump(tp.getWriteFd().getFileDescriptor(), args); + service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args); tp.go(fd); } catch (Throwable e) { if (tp != null) { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 5e3dc023f233..4825c56bab2b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -152,7 +152,7 @@ public final class ActivityThread { private static final int LOG_ON_PAUSE_CALLED = 30021; private static final int LOG_ON_RESUME_CALLED = 30022; - static ContextImpl mSystemContext = null; + private ContextImpl mSystemContext; static IPackageManager sPackageManager; @@ -1645,7 +1645,7 @@ public final class ActivityThread { ? mBoundApplication.processName : null) + ")"); packageInfo = - new LoadedApk(this, aInfo, compatInfo, this, baseLoader, + new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0); if (includeCode) { @@ -1698,26 +1698,15 @@ public final class ActivityThread { public ContextImpl getSystemContext() { synchronized (this) { if (mSystemContext == null) { - ContextImpl context = - ContextImpl.createSystemContext(this); - LoadedApk info = new LoadedApk(this, "android", context, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO); - context.init(info, null, this); - context.getResources().updateConfiguration(mResourcesManager.getConfiguration(), - mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY)); - mSystemContext = context; - //Slog.i(TAG, "Created system resources " + context.getResources() - // + ": " + context.getResources().getConfiguration()); + mSystemContext = ContextImpl.createSystemContext(this); } + return mSystemContext; } - return mSystemContext; } public void installSystemApplicationInfo(ApplicationInfo info) { synchronized (this) { - ContextImpl context = getSystemContext(); - context.init(new LoadedApk(this, "android", context, info, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO), null, this); + getSystemContext().installSystemApplicationInfo(info); // give ourselves a default profiler mProfiler = new Profiler(); @@ -2203,8 +2192,7 @@ public final class ActivityThread { private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) { - ContextImpl appContext = new ContextImpl(); - appContext.init(r.packageInfo, r.token, this); + ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token); appContext.setOuterContext(activity); // For debugging purposes, if the activity's package name contains the value of @@ -2489,8 +2477,7 @@ public final class ActivityThread { agent = (BackupAgent) cl.loadClass(classname).newInstance(); // set up the agent's context - ContextImpl context = new ContextImpl(); - context.init(packageInfo, null, this); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(agent); agent.attach(context); @@ -2562,11 +2549,10 @@ public final class ActivityThread { try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); - ContextImpl context = new ContextImpl(); - context.init(packageInfo, null, this); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); + context.setOuterContext(service); Application app = packageInfo.makeApplication(false, mInstrumentation); - context.setOuterContext(service); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); service.onCreate(); @@ -4162,8 +4148,7 @@ public final class ActivityThread { } updateDefaultDensity(); - final ContextImpl appContext = new ContextImpl(); - appContext.init(data.info, null, this); + final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); if (!Process.isIsolated()) { final File cacheDir = appContext.getCacheDir(); @@ -4274,8 +4259,7 @@ public final class ActivityThread { instrApp.nativeLibraryDir = ii.nativeLibraryDir; LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false, true); - ContextImpl instrContext = new ContextImpl(); - instrContext.init(pi, null, this); + ContextImpl instrContext = ContextImpl.createAppContext(this, pi); try { java.lang.ClassLoader cl = instrContext.getClassLoader(); @@ -4890,8 +4874,8 @@ public final class ActivityThread { UserHandle.myUserId()); try { mInstrumentation = new Instrumentation(); - ContextImpl context = new ContextImpl(); - context.init(getSystemContext().mPackageInfo, null, this); + ContextImpl context = ContextImpl.createAppContext( + this, getSystemContext().mPackageInfo); Application app = Instrumentation.newApplication(Application.class, context); mAllApplications.add(app); mInitialApplication = app; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 190ddb4b879a..d9cad3b8195a 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -183,22 +183,31 @@ class ContextImpl extends Context { */ private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs; - /*package*/ LoadedApk mPackageInfo; - private String mBasePackageName; - private String mOpPackageName; - private Resources mResources; - /*package*/ ActivityThread mMainThread; + final ActivityThread mMainThread; + final LoadedApk mPackageInfo; + + private final IBinder mActivityToken; + + private final UserHandle mUser; + + private final ApplicationContentResolver mContentResolver; + + private final String mBasePackageName; + private final String mOpPackageName; + + private final ResourcesManager mResourcesManager; + private final Resources mResources; + private final Display mDisplay; // may be null if default display + private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments(); + private final Configuration mOverrideConfiguration; + + private final boolean mRestricted; + private Context mOuterContext; - private IBinder mActivityToken = null; - private ApplicationContentResolver mContentResolver; private int mThemeResource = 0; private Resources.Theme mTheme = null; private PackageManager mPackageManager; - private Display mDisplay; // may be null if default display private Context mReceiverRestrictedContext = null; - private boolean mRestricted; - private UserHandle mUser; - private ResourcesManager mResourcesManager; private final Object mSync = new Object(); @@ -220,8 +229,6 @@ class ContextImpl extends Context { private static final String[] EMPTY_FILE_LIST = {}; - final private DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments(); - /** * Override this class when the system service constructor needs a * ContextImpl. Else, use StaticServiceFetcher below. @@ -356,10 +363,11 @@ class ContextImpl extends Context { ctx.mMainThread.getHandler()); }}); - registerService(CONNECTIVITY_SERVICE, new StaticServiceFetcher() { - public Object createStaticService() { + registerService(CONNECTIVITY_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(CONNECTIVITY_SERVICE); - return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b)); + return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b), + ctx.getPackageName()); }}); registerService(COUNTRY_DETECTOR, new StaticServiceFetcher() { @@ -1878,20 +1886,17 @@ class ContextImpl extends Context { @Override public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) throws NameNotFoundException { + final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED; if (packageName.equals("system") || packageName.equals("android")) { - final ContextImpl context = new ContextImpl(mMainThread.getSystemContext()); - context.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED; - context.init(mPackageInfo, null, mMainThread, mResources, mBasePackageName, user); - return context; + return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, + user, restricted, mDisplay, mOverrideConfiguration); } - LoadedApk pi = - mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags, - user.getIdentifier()); + LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), + flags, user.getIdentifier()); if (pi != null) { - ContextImpl c = new ContextImpl(); - c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED; - c.init(pi, null, mMainThread, mResources, mBasePackageName, user); + ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken, + user, restricted, mDisplay, mOverrideConfiguration); if (c.mResources != null) { return c; } @@ -1899,7 +1904,7 @@ class ContextImpl extends Context { // Should be a better exception. throw new PackageManager.NameNotFoundException( - "Application package " + packageName + " not found"); + "Application package " + packageName + " not found"); } @Override @@ -1908,12 +1913,8 @@ class ContextImpl extends Context { throw new IllegalArgumentException("overrideConfiguration must not be null"); } - ContextImpl c = new ContextImpl(); - c.init(mPackageInfo, null, mMainThread); - c.mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(), - getDisplayId(), overrideConfiguration, mResources.getCompatibilityInfo(), - mActivityToken); - return c; + return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, + mUser, mRestricted, mDisplay, overrideConfiguration); } @Override @@ -1922,15 +1923,8 @@ class ContextImpl extends Context { throw new IllegalArgumentException("display must not be null"); } - int displayId = display.getDisplayId(); - - ContextImpl context = new ContextImpl(); - context.init(mPackageInfo, null, mMainThread); - context.mDisplay = display; - DisplayAdjustments daj = getDisplayAdjustments(displayId); - context.mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(), - displayId, null, daj.getCompatibilityInfo(), null); - return context; + return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, + mUser, mRestricted, display, mOverrideConfiguration); } private int getDisplayId() { @@ -1972,43 +1966,76 @@ class ContextImpl extends Context { } static ContextImpl createSystemContext(ActivityThread mainThread) { - final ContextImpl context = new ContextImpl(); - context.init(Resources.getSystem(), mainThread, Process.myUserHandle()); + LoadedApk packageInfo = new LoadedApk(mainThread); + ContextImpl context = new ContextImpl(null, mainThread, + packageInfo, null, null, false, null, null); + context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), + context.mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY)); return context; } - ContextImpl() { - mOuterContext = this; + static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); + return new ContextImpl(null, mainThread, + packageInfo, null, null, false, null, null); } - /** - * Create a new ApplicationContext from an existing one. The new one - * works and operates the same as the one it is copying. - * - * @param context Existing application context. - */ - public ContextImpl(ContextImpl context) { - mPackageInfo = context.mPackageInfo; - mBasePackageName = context.mBasePackageName; - mOpPackageName = context.mOpPackageName; - mResources = context.mResources; - mMainThread = context.mMainThread; - mContentResolver = context.mContentResolver; - mUser = context.mUser; - mDisplay = context.mDisplay; - mOuterContext = this; - mDisplayAdjustments.setCompatibilityInfo(mPackageInfo.getCompatibilityInfo()); + static ContextImpl createActivityContext(ActivityThread mainThread, + LoadedApk packageInfo, IBinder activityToken) { + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); + if (activityToken == null) throw new IllegalArgumentException("activityInfo"); + return new ContextImpl(null, mainThread, + packageInfo, activityToken, null, false, null, null); } - final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) { - init(packageInfo, activityToken, mainThread, null, null, Process.myUserHandle()); - } + private ContextImpl(ContextImpl container, ActivityThread mainThread, + LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted, + Display display, Configuration overrideConfiguration) { + mOuterContext = this; + + mMainThread = mainThread; + mActivityToken = activityToken; + mRestricted = restricted; + + if (user == null) { + user = Process.myUserHandle(); + } + mUser = user; - final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread, - Resources container, String basePackageName, UserHandle user) { mPackageInfo = packageInfo; - if (basePackageName != null) { - mBasePackageName = mOpPackageName = basePackageName; + mContentResolver = new ApplicationContentResolver(this, mainThread, user); + mResourcesManager = ResourcesManager.getInstance(); + mDisplay = display; + mOverrideConfiguration = overrideConfiguration; + + final int displayId = getDisplayId(); + CompatibilityInfo compatInfo = null; + if (container != null) { + compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo(); + } + if (compatInfo == null && displayId == Display.DEFAULT_DISPLAY) { + compatInfo = packageInfo.getCompatibilityInfo(); + } + mDisplayAdjustments.setCompatibilityInfo(compatInfo); + mDisplayAdjustments.setActivityToken(activityToken); + + Resources resources = packageInfo.getResources(mainThread); + if (resources != null) { + if (activityToken != null + || displayId != Display.DEFAULT_DISPLAY + || overrideConfiguration != null + || (compatInfo != null && compatInfo.applicationScale + != resources.getCompatibilityInfo().applicationScale)) { + resources = mResourcesManager.getTopLevelResources( + packageInfo.getResDir(), displayId, + overrideConfiguration, compatInfo, activityToken); + } + } + mResources = resources; + + if (container != null) { + mBasePackageName = container.mBasePackageName; + mOpPackageName = container.mOpPackageName; } else { mBasePackageName = packageInfo.mPackageName; ApplicationInfo ainfo = packageInfo.getApplicationInfo(); @@ -2022,44 +2049,10 @@ class ContextImpl extends Context { mOpPackageName = mBasePackageName; } } - mResources = mPackageInfo.getResources(mainThread); - mResourcesManager = ResourcesManager.getInstance(); - - CompatibilityInfo compatInfo = - container == null ? null : container.getCompatibilityInfo(); - if (mResources != null && - ((compatInfo != null && compatInfo.applicationScale != - mResources.getCompatibilityInfo().applicationScale) - || activityToken != null)) { - if (DEBUG) { - Log.d(TAG, "loaded context has different scaling. Using container's" + - " compatiblity info:" + container.getDisplayMetrics()); - } - if (compatInfo == null) { - compatInfo = packageInfo.getCompatibilityInfo(); - } - mDisplayAdjustments.setCompatibilityInfo(compatInfo); - mDisplayAdjustments.setActivityToken(activityToken); - mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(), - Display.DEFAULT_DISPLAY, null, compatInfo, activityToken); - } else { - mDisplayAdjustments.setCompatibilityInfo(packageInfo.getCompatibilityInfo()); - mDisplayAdjustments.setActivityToken(activityToken); - } - mMainThread = mainThread; - mActivityToken = activityToken; - mContentResolver = new ApplicationContentResolver(this, mainThread, user); - mUser = user; } - final void init(Resources resources, ActivityThread mainThread, UserHandle user) { - mPackageInfo = null; - mBasePackageName = null; - mOpPackageName = null; - mResources = resources; - mMainThread = mainThread; - mContentResolver = new ApplicationContentResolver(this, mainThread, user); - mUser = user; + void installSystemApplicationInfo(ApplicationInfo info) { + mPackageInfo.installSystemApplicationInfo(info); } final void scheduleFinalCleanup(String who, String what) { diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 22a21cdf829d..aab6ed83b11f 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -205,7 +205,9 @@ public class KeyguardManager { try { mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() { public void onKeyguardExitResult(boolean success) throws RemoteException { - callback.onKeyguardExitResult(success); + if (callback != null) { + callback.onKeyguardExitResult(success); + } } }); } catch (RemoteException e) { diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 4239a5d8d0b7..8b0fd87b6bbd 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -72,7 +72,7 @@ public final class LoadedApk { private static final String TAG = "LoadedApk"; private final ActivityThread mActivityThread; - private final ApplicationInfo mApplicationInfo; + private ApplicationInfo mApplicationInfo; final String mPackageName; private final String mAppDir; private final String mResDir; @@ -110,8 +110,7 @@ public final class LoadedApk { * so MUST NOT call back out to the activity manager. */ public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo, - CompatibilityInfo compatInfo, - ActivityThread mainThread, ClassLoader baseLoader, + CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode) { mActivityThread = activityThread; mApplicationInfo = aInfo; @@ -132,31 +131,17 @@ public final class LoadedApk { mSecurityViolation = securityViolation; mIncludeCode = includeCode; mDisplayAdjustments.setCompatibilityInfo(compatInfo); - - if (mAppDir == null) { - if (ActivityThread.mSystemContext == null) { - ActivityThread.mSystemContext = - ContextImpl.createSystemContext(mainThread); - ResourcesManager resourcesManager = ResourcesManager.getInstance(); - ActivityThread.mSystemContext.getResources().updateConfiguration( - resourcesManager.getConfiguration(), - resourcesManager.getDisplayMetricsLocked( - Display.DEFAULT_DISPLAY, mDisplayAdjustments), compatInfo); - //Slog.i(TAG, "Created system resources " - // + mSystemContext.getResources() + ": " - // + mSystemContext.getResources().getConfiguration()); - } - mClassLoader = ActivityThread.mSystemContext.getClassLoader(); - mResources = ActivityThread.mSystemContext.getResources(); - } } - public LoadedApk(ActivityThread activityThread, String name, - Context systemContext, ApplicationInfo info, CompatibilityInfo compatInfo) { + /** + * Create information about the system package. + * Must call {@link #installSystemApplicationInfo} later. + */ + LoadedApk(ActivityThread activityThread) { mActivityThread = activityThread; - mApplicationInfo = info != null ? info : new ApplicationInfo(); - mApplicationInfo.packageName = name; - mPackageName = name; + mApplicationInfo = new ApplicationInfo(); + mApplicationInfo.packageName = "android"; + mPackageName = "android"; mAppDir = null; mResDir = null; mSharedLibraries = null; @@ -166,9 +151,16 @@ public final class LoadedApk { mBaseClassLoader = null; mSecurityViolation = false; mIncludeCode = true; - mClassLoader = systemContext.getClassLoader(); - mResources = systemContext.getResources(); - mDisplayAdjustments.setCompatibilityInfo(compatInfo); + mClassLoader = ClassLoader.getSystemClassLoader(); + mResources = Resources.getSystem(); + } + + /** + * Sets application info about the system package. + */ + void installSystemApplicationInfo(ApplicationInfo info) { + assert info.packageName.equals("android"); + mApplicationInfo = info; } public String getPackageName() { @@ -506,8 +498,7 @@ public final class LoadedApk { try { java.lang.ClassLoader cl = getClassLoader(); - ContextImpl appContext = new ContextImpl(); - appContext.init(this, null, mActivityThread); + ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java index 63b641cb708e..dffa969a7148 100644 --- a/core/java/android/app/MediaRouteActionProvider.java +++ b/core/java/android/app/MediaRouteActionProvider.java @@ -16,10 +16,7 @@ package android.app; -import com.android.internal.app.MediaRouteChooserDialogFragment; - import android.content.Context; -import android.content.ContextWrapper; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.util.Log; @@ -30,22 +27,38 @@ import android.view.ViewGroup; import java.lang.ref.WeakReference; +/** + * The media route action provider displays a {@link MediaRouteButton media route button} + * in the application's {@link ActionBar} to allow the user to select routes and + * to control the currently selected route. + * <p> + * The application must specify the kinds of routes that the user should be allowed + * to select by specifying the route types with the {@link #setRouteTypes} method. + * </p><p> + * Refer to {@link MediaRouteButton} for a description of the button that will + * appear in the action bar menu. Note that instead of disabling the button + * when no routes are available, the action provider will instead make the + * menu item invisible. In this way, the button will only be visible when it + * is possible for the user to discover and select a matching route. + * </p> + */ public class MediaRouteActionProvider extends ActionProvider { private static final String TAG = "MediaRouteActionProvider"; - private Context mContext; - private MediaRouter mRouter; - private MenuItem mMenuItem; - private MediaRouteButton mView; + private final Context mContext; + private final MediaRouter mRouter; + private final MediaRouterCallback mCallback; + private int mRouteTypes; + private MediaRouteButton mButton; private View.OnClickListener mExtendedSettingsListener; - private RouterCallback mCallback; public MediaRouteActionProvider(Context context) { super(context); + mContext = context; mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); - mCallback = new RouterCallback(this); + mCallback = new MediaRouterCallback(this); // Start with live audio by default. // TODO Update this when new route types are added; segment by API level @@ -53,80 +66,74 @@ public class MediaRouteActionProvider extends ActionProvider { setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO); } + /** + * Sets the types of routes that will be shown in the media route chooser dialog + * launched by this button. + * + * @param types The route types to match. + */ public void setRouteTypes(int types) { - if (mRouteTypes == types) return; - if (mRouteTypes != 0) { - mRouter.removeCallback(mCallback); - } - mRouteTypes = types; - if (types != 0) { - mRouter.addCallback(types, mCallback); + if (mRouteTypes != types) { + // FIXME: We currently have no way of knowing whether the action provider + // is still needed by the UI. Unfortunately this means the action provider + // may leak callbacks until garbage collection occurs. This may result in + // media route providers doing more work than necessary in the short term + // while trying to discover routes that are no longer of interest to the + // application. To solve this problem, the action provider will need some + // indication from the framework that it is being destroyed. + if (mRouteTypes != 0) { + mRouter.removeCallback(mCallback); + } + mRouteTypes = types; + if (types != 0) { + mRouter.addCallback(types, mCallback, + MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); + } + refreshRoute(); + + if (mButton != null) { + mButton.setRouteTypes(mRouteTypes); + } } - if (mView != null) { - mView.setRouteTypes(mRouteTypes); + } + + public void setExtendedSettingsClickListener(View.OnClickListener listener) { + mExtendedSettingsListener = listener; + if (mButton != null) { + mButton.setExtendedSettingsClickListener(listener); } } @Override + @SuppressWarnings("deprecation") public View onCreateActionView() { throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead."); } @Override public View onCreateActionView(MenuItem item) { - if (mMenuItem != null || mView != null) { + if (mButton != null) { Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " + "with a menu item. Don't reuse MediaRouteActionProvider instances! " + "Abandoning the old one..."); } - mMenuItem = item; - mView = new MediaRouteButton(mContext); - mView.setCheatSheetEnabled(true); - mView.setRouteTypes(mRouteTypes); - mView.setExtendedSettingsClickListener(mExtendedSettingsListener); - mView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + + mButton = new MediaRouteButton(mContext); + mButton.setCheatSheetEnabled(true); + mButton.setRouteTypes(mRouteTypes); + mButton.setExtendedSettingsClickListener(mExtendedSettingsListener); + mButton.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); - return mView; + return mButton; } @Override public boolean onPerformDefaultAction() { - final FragmentManager fm = getActivity().getFragmentManager(); - // See if one is already attached to this activity. - MediaRouteChooserDialogFragment dialogFragment = - (MediaRouteChooserDialogFragment) fm.findFragmentByTag( - MediaRouteChooserDialogFragment.FRAGMENT_TAG); - if (dialogFragment != null) { - Log.w(TAG, "onPerformDefaultAction(): Chooser dialog already showing!"); - return false; - } - - dialogFragment = new MediaRouteChooserDialogFragment(); - dialogFragment.setExtendedSettingsClickListener(mExtendedSettingsListener); - dialogFragment.setRouteTypes(mRouteTypes); - dialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); - return true; - } - - private Activity getActivity() { - // Gross way of unwrapping the Activity so we can get the FragmentManager - Context context = mContext; - while (context instanceof ContextWrapper && !(context instanceof Activity)) { - context = ((ContextWrapper) context).getBaseContext(); - } - if (!(context instanceof Activity)) { - throw new IllegalStateException("The MediaRouteActionProvider's Context " + - "is not an Activity."); - } - - return (Activity) context; - } - - public void setExtendedSettingsClickListener(View.OnClickListener listener) { - mExtendedSettingsListener = listener; - if (mView != null) { - mView.setExtendedSettingsClickListener(listener); + if (mButton != null) { + return mButton.showDialogInternal(); } + return false; } @Override @@ -136,36 +143,43 @@ public class MediaRouteActionProvider extends ActionProvider { @Override public boolean isVisible() { - return mRouter.getRouteCount() > 1; + return mRouter.isRouteAvailable(mRouteTypes, + MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE); + } + + private void refreshRoute() { + refreshVisibility(); } - private static class RouterCallback extends MediaRouter.SimpleCallback { - private WeakReference<MediaRouteActionProvider> mAp; + private static class MediaRouterCallback extends MediaRouter.SimpleCallback { + private final WeakReference<MediaRouteActionProvider> mProviderWeak; - RouterCallback(MediaRouteActionProvider ap) { - mAp = new WeakReference<MediaRouteActionProvider>(ap); + public MediaRouterCallback(MediaRouteActionProvider provider) { + mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider); } @Override public void onRouteAdded(MediaRouter router, RouteInfo info) { - final MediaRouteActionProvider ap = mAp.get(); - if (ap == null) { - router.removeCallback(this); - return; - } - - ap.refreshVisibility(); + refreshRoute(router); } @Override public void onRouteRemoved(MediaRouter router, RouteInfo info) { - final MediaRouteActionProvider ap = mAp.get(); - if (ap == null) { + refreshRoute(router); + } + + @Override + public void onRouteChanged(MediaRouter router, RouteInfo info) { + refreshRoute(router); + } + + private void refreshRoute(MediaRouter router) { + MediaRouteActionProvider provider = mProviderWeak.get(); + if (provider != null) { + provider.refreshRoute(); + } else { router.removeCallback(this); - return; } - - ap.refreshVisibility(); } } } diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java index 7e0a27aca508..a7982f4584fc 100644 --- a/core/java/android/app/MediaRouteButton.java +++ b/core/java/android/app/MediaRouteButton.java @@ -17,7 +17,7 @@ package android.app; import com.android.internal.R; -import com.android.internal.app.MediaRouteChooserDialogFragment; +import com.android.internal.app.MediaRouteDialogPresenter; import android.content.Context; import android.content.ContextWrapper; @@ -30,7 +30,6 @@ import android.media.MediaRouter.RouteGroup; import android.media.MediaRouter.RouteInfo; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.SoundEffectConstants; @@ -38,17 +37,15 @@ import android.view.View; import android.widget.Toast; public class MediaRouteButton extends View { - private static final String TAG = "MediaRouteButton"; + private final MediaRouter mRouter; + private final MediaRouterCallback mCallback; - private MediaRouter mRouter; - private final MediaRouteCallback mRouterCallback = new MediaRouteCallback(); private int mRouteTypes; private boolean mAttachedToWindow; private Drawable mRemoteIndicator; private boolean mRemoteActive; - private boolean mToggleMode; private boolean mCheatSheetEnabled; private boolean mIsConnecting; @@ -56,12 +53,13 @@ public class MediaRouteButton extends View { private int mMinHeight; private OnClickListener mExtendedSettingsClickListener; - private MediaRouteChooserDialogFragment mDialogFragment; + // The checked state is used when connected to a remote route. private static final int[] CHECKED_STATE_SET = { R.attr.state_checked }; + // The activated state is used while connecting to a remote route. private static final int[] ACTIVATED_STATE_SET = { R.attr.state_activated }; @@ -78,6 +76,7 @@ public class MediaRouteButton extends View { super(context, attrs, defStyleAttr); mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + mCallback = new MediaRouterCallback(); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0); @@ -98,54 +97,100 @@ public class MediaRouteButton extends View { setRouteTypes(routeTypes); } - private void setRemoteIndicatorDrawable(Drawable d) { - if (mRemoteIndicator != null) { - mRemoteIndicator.setCallback(null); - unscheduleDrawable(mRemoteIndicator); - } - mRemoteIndicator = d; - if (d != null) { - d.setCallback(this); - d.setState(getDrawableState()); - d.setVisible(getVisibility() == VISIBLE, false); + /** + * Gets the media route types for filtering the routes that the user can + * select using the media route chooser dialog. + * + * @return The route types. + */ + public int getRouteTypes() { + return mRouteTypes; + } + + /** + * Sets the types of routes that will be shown in the media route chooser dialog + * launched by this button. + * + * @param types The route types to match. + */ + public void setRouteTypes(int types) { + if (mRouteTypes != types) { + if (mAttachedToWindow && mRouteTypes != 0) { + mRouter.removeCallback(mCallback); + } + + mRouteTypes = types; + + if (mAttachedToWindow && types != 0) { + mRouter.addCallback(types, mCallback, + MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); + } + + refreshRoute(); } + } - refreshDrawableState(); + public void setExtendedSettingsClickListener(OnClickListener listener) { + mExtendedSettingsClickListener = listener; } - @Override - public boolean performClick() { - // Send the appropriate accessibility events and call listeners - boolean handled = super.performClick(); - if (!handled) { - playSoundEffect(SoundEffectConstants.CLICK); + /** + * Show the route chooser or controller dialog. + * <p> + * If the default route is selected or if the currently selected route does + * not match the {@link #getRouteTypes route types}, then shows the route chooser dialog. + * Otherwise, shows the route controller dialog to offer the user + * a choice to disconnect from the route or perform other control actions + * such as setting the route's volume. + * </p><p> + * This will attach a {@link DialogFragment} to the containing Activity. + * </p> + */ + public void showDialog() { + showDialogInternal(); + } + + boolean showDialogInternal() { + if (!mAttachedToWindow) { + return false; } - if (mToggleMode) { - if (mRemoteActive) { - mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute()); - } else { - final int N = mRouter.getRouteCount(); - for (int i = 0; i < N; i++) { - final RouteInfo route = mRouter.getRouteAt(i); - if ((route.getSupportedTypes() & mRouteTypes) != 0 && - route != mRouter.getDefaultRoute()) { - mRouter.selectRouteInt(mRouteTypes, route); - } - } + DialogFragment f = MediaRouteDialogPresenter.showDialogFragment(getActivity(), + mRouteTypes, mExtendedSettingsClickListener); + return f != null; + } + + private Activity getActivity() { + // Gross way of unwrapping the Activity so we can get the FragmentManager + Context context = getContext(); + while (context instanceof ContextWrapper) { + if (context instanceof Activity) { + return (Activity)context; } - } else { - showDialog(); + context = ((ContextWrapper)context).getBaseContext(); } - - return handled; + throw new IllegalStateException("The MediaRouteButton's Context is not an Activity."); } + /** + * Sets whether to enable showing a toast with the content descriptor of the + * button when the button is long pressed. + */ void setCheatSheetEnabled(boolean enable) { mCheatSheetEnabled = enable; } @Override + public boolean performClick() { + // Send the appropriate accessibility events and call listeners + boolean handled = super.performClick(); + if (!handled) { + playSoundEffect(SoundEffectConstants.CLICK); + } + return showDialogInternal() || handled; + } + + @Override public boolean performLongClick() { if (super.performLongClick()) { return true; @@ -183,85 +228,9 @@ public class MediaRouteButton extends View { } cheatSheet.show(); performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - return true; } - public void setRouteTypes(int types) { - if (types == mRouteTypes) { - // Already registered; nothing to do. - return; - } - - if (mAttachedToWindow && mRouteTypes != 0) { - mRouter.removeCallback(mRouterCallback); - } - - mRouteTypes = types; - - if (mAttachedToWindow) { - updateRouteInfo(); - mRouter.addCallback(types, mRouterCallback); - } - } - - private void updateRouteInfo() { - updateRemoteIndicator(); - updateRouteCount(); - } - - public int getRouteTypes() { - return mRouteTypes; - } - - void updateRemoteIndicator() { - final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes); - final boolean isRemote = selected != mRouter.getDefaultRoute(); - final boolean isConnecting = selected != null && - selected.getStatusCode() == RouteInfo.STATUS_CONNECTING; - - boolean needsRefresh = false; - if (mRemoteActive != isRemote) { - mRemoteActive = isRemote; - needsRefresh = true; - } - if (mIsConnecting != isConnecting) { - mIsConnecting = isConnecting; - needsRefresh = true; - } - - if (needsRefresh) { - refreshDrawableState(); - } - } - - void updateRouteCount() { - final int N = mRouter.getRouteCount(); - int count = 0; - boolean hasVideoRoutes = false; - for (int i = 0; i < N; i++) { - final RouteInfo route = mRouter.getRouteAt(i); - final int routeTypes = route.getSupportedTypes(); - if ((routeTypes & mRouteTypes) != 0) { - if (route instanceof RouteGroup) { - count += ((RouteGroup) route).getRouteCount(); - } else { - count++; - } - if ((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0) { - hasVideoRoutes = true; - } - } - } - - setEnabled(count != 0); - - // Only allow toggling if we have more than just user routes. - // Don't toggle if we support video routes, we may have to let the dialog scan. - mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 && - !hasVideoRoutes; - } - @Override protected int[] onCreateDrawableState(int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); @@ -289,6 +258,21 @@ public class MediaRouteButton extends View { } } + private void setRemoteIndicatorDrawable(Drawable d) { + if (mRemoteIndicator != null) { + mRemoteIndicator.setCallback(null); + unscheduleDrawable(mRemoteIndicator); + } + mRemoteIndicator = d; + if (d != null) { + d.setCallback(this); + d.setState(getDrawableState()); + d.setVisible(getVisibility() == VISIBLE, false); + } + + refreshDrawableState(); + } + @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mRemoteIndicator; @@ -297,12 +281,16 @@ public class MediaRouteButton extends View { @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); - if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState(); + + if (mRemoteIndicator != null) { + mRemoteIndicator.jumpToCurrentState(); + } } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); + if (mRemoteIndicator != null) { mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false); } @@ -311,19 +299,22 @@ public class MediaRouteButton extends View { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); + mAttachedToWindow = true; if (mRouteTypes != 0) { - mRouter.addCallback(mRouteTypes, mRouterCallback); - updateRouteInfo(); + mRouter.addCallback(mRouteTypes, mCallback, + MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); } + refreshRoute(); } @Override public void onDetachedFromWindow() { + mAttachedToWindow = false; if (mRouteTypes != 0) { - mRouter.removeCallback(mRouterCallback); + mRouter.removeCallback(mCallback); } - mAttachedToWindow = false; + super.onDetachedFromWindow(); } @@ -386,93 +377,71 @@ public class MediaRouteButton extends View { final int drawLeft = left + (right - left - drawWidth) / 2; final int drawTop = top + (bottom - top - drawHeight) / 2; - mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight); + mRemoteIndicator.setBounds(drawLeft, drawTop, + drawLeft + drawWidth, drawTop + drawHeight); mRemoteIndicator.draw(canvas); } - public void setExtendedSettingsClickListener(OnClickListener listener) { - mExtendedSettingsClickListener = listener; - if (mDialogFragment != null) { - mDialogFragment.setExtendedSettingsClickListener(listener); - } - } - - /** - * Asynchronously show the route chooser dialog. - * This will attach a {@link DialogFragment} to the containing Activity. - */ - public void showDialog() { - final FragmentManager fm = getActivity().getFragmentManager(); - if (mDialogFragment == null) { - // See if one is already attached to this activity. - mDialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag( - MediaRouteChooserDialogFragment.FRAGMENT_TAG); - } - if (mDialogFragment != null) { - Log.w(TAG, "showDialog(): Already showing!"); - return; - } + private void refreshRoute() { + if (mAttachedToWindow) { + final MediaRouter.RouteInfo route = mRouter.getSelectedRoute(); + final boolean isRemote = !route.isDefault() && route.matchesTypes(mRouteTypes); + final boolean isConnecting = isRemote && route.isConnecting(); + + boolean needsRefresh = false; + if (mRemoteActive != isRemote) { + mRemoteActive = isRemote; + needsRefresh = true; + } + if (mIsConnecting != isConnecting) { + mIsConnecting = isConnecting; + needsRefresh = true; + } - mDialogFragment = new MediaRouteChooserDialogFragment(); - mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener); - mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() { - @Override - public void onDetached(MediaRouteChooserDialogFragment detachedFragment) { - mDialogFragment = null; + if (needsRefresh) { + refreshDrawableState(); } - }); - mDialogFragment.setRouteTypes(mRouteTypes); - mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); - } - private Activity getActivity() { - // Gross way of unwrapping the Activity so we can get the FragmentManager - Context context = getContext(); - while (context instanceof ContextWrapper && !(context instanceof Activity)) { - context = ((ContextWrapper) context).getBaseContext(); + setEnabled(mRouter.isRouteAvailable(mRouteTypes, + MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE)); } - if (!(context instanceof Activity)) { - throw new IllegalStateException("The MediaRouteButton's Context is not an Activity."); - } - - return (Activity) context; } - private class MediaRouteCallback extends MediaRouter.SimpleCallback { + private final class MediaRouterCallback extends MediaRouter.SimpleCallback { @Override - public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { - updateRemoteIndicator(); + public void onRouteAdded(MediaRouter router, RouteInfo info) { + refreshRoute(); } @Override - public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { - updateRemoteIndicator(); + public void onRouteRemoved(MediaRouter router, RouteInfo info) { + refreshRoute(); } @Override public void onRouteChanged(MediaRouter router, RouteInfo info) { - updateRemoteIndicator(); + refreshRoute(); } @Override - public void onRouteAdded(MediaRouter router, RouteInfo info) { - updateRouteCount(); + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + refreshRoute(); } @Override - public void onRouteRemoved(MediaRouter router, RouteInfo info) { - updateRouteCount(); + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + refreshRoute(); } @Override public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index) { - updateRouteCount(); + refreshRoute(); } @Override public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { - updateRouteCount(); + refreshRoute(); } } } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index c63e586a7127..cce6fc42da58 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1778,7 +1778,7 @@ public class Notification implements Parcelable RemoteViews button = new RemoteViews(mContext.getPackageName(), tombstone ? R.layout.notification_action_tombstone : R.layout.notification_action); - button.setTextViewCompoundDrawables(R.id.action0, action.icon, 0, 0, 0); + button.setTextViewCompoundDrawablesRelative(R.id.action0, action.icon, 0, 0, 0); button.setTextViewText(R.id.action0, action.title); if (!tombstone) { button.setOnClickPendingIntent(R.id.action0, action.actionIntent); diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 7bcf43eb8d03..2045ed8411a3 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -58,10 +58,7 @@ public class StatusBarManager { | DISABLE_SYSTEM_INFO | DISABLE_RECENT | DISABLE_HOME | DISABLE_BACK | DISABLE_CLOCK | DISABLE_SEARCH; - public static final int NAVIGATION_HINT_BACK_NOP = 1 << 0; - public static final int NAVIGATION_HINT_HOME_NOP = 1 << 1; - public static final int NAVIGATION_HINT_RECENT_NOP = 1 << 2; - public static final int NAVIGATION_HINT_BACK_ALT = 1 << 3; + public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0; public static final int WINDOW_STATUS_BAR = 1; public static final int WINDOW_NAVIGATION_BAR = 2; diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 607930c0be44..91b0d7cd68ba 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -146,7 +146,9 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { @Override public void shutdown() { synchronized (mLock) { - throwIfCalledByNotTrustedUidLocked(); + if (isConnectedLocked()) { + throwIfCalledByNotTrustedUidLocked(); + } throwIfShutdownLocked(); mIsShutdown = true; if (isConnectedLocked()) { diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index ced72f80a029..f291e8298bc1 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -45,9 +45,7 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.DisplayMetrics; import android.util.Log; -import android.view.WindowManager; import android.view.WindowManagerGlobal; import java.io.BufferedInputStream; @@ -294,9 +292,8 @@ public class WallpaperManager { try { BitmapFactory.Options options = new BitmapFactory.Options(); - Bitmap bm = BitmapFactory.decodeFileDescriptor( + return BitmapFactory.decodeFileDescriptor( fd.getFileDescriptor(), null, options); - return generateBitmap(context, bm, width, height); } catch (OutOfMemoryError e) { Log.w(TAG, "Can't decode file", e); } finally { @@ -323,8 +320,7 @@ public class WallpaperManager { try { BitmapFactory.Options options = new BitmapFactory.Options(); - Bitmap bm = BitmapFactory.decodeStream(is, null, options); - return generateBitmap(context, bm, width, height); + return BitmapFactory.decodeStream(is, null, options); } catch (OutOfMemoryError e) { Log.w(TAG, "Can't decode stream", e); } finally { @@ -1029,62 +1025,4 @@ public class WallpaperManager { public void clear() throws IOException { setResource(com.android.internal.R.drawable.default_wallpaper); } - - static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) { - if (bm == null) { - return null; - } - - WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); - DisplayMetrics metrics = new DisplayMetrics(); - wm.getDefaultDisplay().getMetrics(metrics); - bm.setDensity(metrics.noncompatDensityDpi); - - if (width <= 0 || height <= 0 - || (bm.getWidth() == width && bm.getHeight() == height)) { - return bm; - } - - // This is the final bitmap we want to return. - try { - Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - newbm.setDensity(metrics.noncompatDensityDpi); - - Canvas c = new Canvas(newbm); - Rect targetRect = new Rect(); - targetRect.right = bm.getWidth(); - targetRect.bottom = bm.getHeight(); - - int deltaw = width - targetRect.right; - int deltah = height - targetRect.bottom; - - if (deltaw > 0 || deltah > 0) { - // We need to scale up so it covers the entire area. - float scale; - if (deltaw > deltah) { - scale = width / (float)targetRect.right; - } else { - scale = height / (float)targetRect.bottom; - } - targetRect.right = (int)(targetRect.right*scale); - targetRect.bottom = (int)(targetRect.bottom*scale); - deltaw = width - targetRect.right; - deltah = height - targetRect.bottom; - } - - targetRect.offset(deltaw/2, deltah/2); - - Paint paint = new Paint(); - paint.setFilterBitmap(true); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); - c.drawBitmap(bm, null, targetRect, paint); - - bm.recycle(); - c.setBitmap(null); - return newbm; - } catch (OutOfMemoryError e) { - Log.w(TAG, "Can't generate default bitmap", e); - return bm; - } - } } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index bb4f5f160bbe..12ee3b624f63 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -43,14 +43,14 @@ interface IBackupManager { void dataChanged(String packageName); /** - * Erase all backed-up data for the given package from the storage + * Erase all backed-up data for the given package from the given storage * destination. * * Any application can invoke this method for its own package, but * only callers who hold the android.permission.BACKUP permission * may invoke it for arbitrary packages. */ - void clearBackupData(String packageName); + void clearBackupData(String transportName, String packageName); /** * Notifies the Backup Manager Service that an agent has become available. This diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index e2bc80aad66d..75b007c75ce3 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -20,21 +20,23 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.util.Pair; + import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.HashMap; -import java.util.LinkedList; +import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Random; @@ -214,6 +216,22 @@ public final class BluetoothAdapter { "android.bluetooth.adapter.action.SCAN_MODE_CHANGED"; /** + * Broadcast Action: Indicate BLE Advertising is started. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BLUETOOTH_ADVERTISING_STARTED = + "android.bluetooth.adapter.action.ADVERTISING_STARTED"; + + /** + * Broadcast Action: Indicated BLE Advertising is stopped. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BLUETOOTH_ADVERTISING_STOPPED = + "android.bluetooth.adapter.action.ADVERTISING_STOPPED"; + + /** * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED} * intents to request the current scan mode. Possible values are: * {@link #SCAN_MODE_NONE}, @@ -251,7 +269,6 @@ public final class BluetoothAdapter { */ public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23; - /** * Broadcast Action: The local Bluetooth adapter has started the remote * device discovery process. @@ -350,9 +367,27 @@ public final class BluetoothAdapter { /** The profile is in disconnecting state */ public static final int STATE_DISCONNECTING = 3; + /** States for Bluetooth LE advertising */ + /** @hide */ + public static final int STATE_ADVERTISE_STARTING = 0; + /** @hide */ + public static final int STATE_ADVERTISE_STARTED = 1; + /** @hide */ + public static final int STATE_ADVERTISE_STOPPING = 2; + /** @hide */ + public static final int STATE_ADVERTISE_STOPPED = 3; + /** + * Force stopping advertising without callback in case the advertising app dies. + * @hide + */ + public static final int STATE_ADVERTISE_FORCE_STOPPING = 4; + /** @hide */ public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager"; + /** @hide */ + public static final int ADVERTISE_CALLBACK_SUCCESS = 0; + private static final int ADDRESS_LENGTH = 17; /** @@ -365,6 +400,10 @@ public final class BluetoothAdapter { private IBluetooth mService; private final Map<LeScanCallback, GattCallbackWrapper> mLeScanClients; + private BluetoothAdvScanData mBluetoothAdvScanData = null; + private GattCallbackWrapper mAdvertisingGattCallback; + private final Handler mHandler; // Handler to post the advertise callback to run on main thread. + private final Object mLock = new Object(); /** * Get a handle to the default local Bluetooth adapter. @@ -400,6 +439,7 @@ public final class BluetoothAdapter { } catch (RemoteException e) {Log.e(TAG, "", e);} mManagerService = managerService; mLeScanClients = new HashMap<LeScanCallback, GattCallbackWrapper>(); + mHandler = new Handler(Looper.getMainLooper()); } /** @@ -438,6 +478,129 @@ public final class BluetoothAdapter { } /** + * Returns a {@link BluetoothAdvScanData} object representing advertising data. + * Data will be reset when bluetooth service is turned off. + * @hide + */ + public BluetoothAdvScanData getAdvScanData() { + try { + IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); + if (iGatt == null) { + // BLE is not supported + Log.e(TAG, "failed to start, iGatt null"); + return null; + } + if (mBluetoothAdvScanData == null) { + mBluetoothAdvScanData = new BluetoothAdvScanData(iGatt, BluetoothAdvScanData.AD); + } + return mBluetoothAdvScanData; + } catch (RemoteException e) { + Log.e(TAG, "failed to get advScanData, error: " + e); + return null; + } + } + + /** + * Interface for BLE advertising callback. + * + * @hide + */ + public interface AdvertiseCallback { + /** + * Callback when advertise starts. + * @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure. + */ + void onAdvertiseStart(int status); + /** + * Callback when advertise stops. + * @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure. + */ + void onAdvertiseStop(int status); + } + + /** + * Start BLE advertising using current {@link BluetoothAdvScanData}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} + * + * @param callback - {@link AdvertiseCallback} + * @return true if BLE advertising succeeds, false otherwise. + * @hide + */ + public boolean startAdvertising(final AdvertiseCallback callback) { + if (getState() != STATE_ON) return false; + try { + IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); + if (iGatt == null) { + // BLE is not supported. + return false; + } + // Restart/reset advertising packets if advertising is in progress. + if (isAdvertising()) { + // Invalid advertising callback. + if (mAdvertisingGattCallback == null || mAdvertisingGattCallback.mLeHandle == -1) { + Log.e(TAG, "failed to restart advertising, invalid callback"); + return false; + } + iGatt.startAdvertising(mAdvertisingGattCallback.mLeHandle); + // Run the callback from main thread. + mHandler.post(new Runnable() { + @Override + public void run() { + // callback with status success. + callback.onAdvertiseStart(ADVERTISE_CALLBACK_SUCCESS); + } + }); + return true; + } + UUID uuid = UUID.randomUUID(); + GattCallbackWrapper wrapper = + new GattCallbackWrapper(this, null, null, callback); + iGatt.registerClient(new ParcelUuid(uuid), wrapper); + if (!wrapper.advertiseStarted()) { + return false; + } + synchronized (mLock) { + mAdvertisingGattCallback = wrapper; + } + return true; + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + } + + /** + * Stop BLE advertising. + * + * @param callback - {@link AdvertiseCallback} + * @return true if BLE advertising stops, false otherwise. + * @hide + */ + public boolean stopAdvertising(AdvertiseCallback callback) { + try { + IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); + if (iGatt == null) { + // BLE is not supported + return false; + } + if (mAdvertisingGattCallback == null) { + // no callback. + return false; + } + // Make sure same callback is used for start and stop advertising. + if (callback != mAdvertisingGattCallback.mAdvertiseCallback) { + Log.e(TAG, "must use the same callback for star/stop advertising"); + return false; + } + mAdvertisingGattCallback.stopAdvertising(); + return true; + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + } + + /** * Return true if Bluetooth is currently enabled and ready for use. * <p>Equivalent to: * <code>getBluetoothState() == STATE_ON</code> @@ -849,6 +1012,23 @@ public final class BluetoothAdapter { } /** + * Returns whether BLE is currently advertising. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. + * + * @hide + */ + public boolean isAdvertising() { + if (getState() != STATE_ON) return false; + try { + IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); + return iGatt.isAdvertising(); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return false; + } + + /** * Return the set of {@link BluetoothDevice} objects that are bonded * (paired) to the local adapter. * <p>If Bluetooth state is not {@link #STATE_ON}, this API @@ -1272,6 +1452,8 @@ public final class BluetoothAdapter { if (VDBG) Log.d(TAG, "onBluetoothServiceDown: " + mService); synchronized (mManagerCallback) { mService = null; + // Reset bluetooth adv scan data when Gatt service is down. + mBluetoothAdvScanData = null; for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){ try { if (cb != null) { @@ -1547,7 +1729,9 @@ public final class BluetoothAdapter { private static final int LE_CALLBACK_REG_TIMEOUT = 2000; private static final int LE_CALLBACK_REG_WAIT_COUNT = 5; + private final AdvertiseCallback mAdvertiseCallback; private final LeScanCallback mLeScanCb; + // mLeHandle 0: not registered // -1: scan stopped // >0: registered and scan started @@ -1561,16 +1745,34 @@ public final class BluetoothAdapter { mLeScanCb = leScanCb; mScanFilter = uuid; mLeHandle = 0; + mAdvertiseCallback = null; + } + + public GattCallbackWrapper(BluetoothAdapter bluetoothAdapter, LeScanCallback leScanCb, + UUID[] uuid, AdvertiseCallback callback) { + mBluetoothAdapter = new WeakReference<BluetoothAdapter>(bluetoothAdapter); + mLeScanCb = leScanCb; + mScanFilter = uuid; + mLeHandle = 0; + mAdvertiseCallback = callback; } public boolean scanStarted() { + return waitForRegisteration(LE_CALLBACK_REG_WAIT_COUNT); + } + + public boolean advertiseStarted() { + // Wait for registeration callback. + return waitForRegisteration(1); + } + + private boolean waitForRegisteration(int maxWaitCount) { boolean started = false; synchronized(this) { if (mLeHandle == -1) return false; - int count = 0; // wait for callback registration and LE scan to start - while (mLeHandle == 0 && count < LE_CALLBACK_REG_WAIT_COUNT) { + while (mLeHandle == 0 && count < maxWaitCount) { try { wait(LE_CALLBACK_REG_TIMEOUT); } catch (InterruptedException e) { @@ -1583,6 +1785,27 @@ public final class BluetoothAdapter { return started; } + public void stopAdvertising() { + synchronized (this) { + if (mLeHandle <= 0) { + Log.e(TAG, "Error state, mLeHandle: " + mLeHandle); + return; + } + BluetoothAdapter adapter = mBluetoothAdapter.get(); + if (adapter != null) { + try { + IBluetoothGatt iGatt = adapter.getBluetoothManager().getBluetoothGatt(); + iGatt.stopAdvertising(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to stop advertising" + e); + } + } else { + Log.e(TAG, "stopAdvertising, BluetoothAdapter is null"); + } + notifyAll(); + } + } + public void stopLeScan() { synchronized(this) { if (mLeHandle <= 0) { @@ -1624,14 +1847,18 @@ public final class BluetoothAdapter { BluetoothAdapter adapter = mBluetoothAdapter.get(); if (adapter != null) { iGatt = adapter.getBluetoothManager().getBluetoothGatt(); - if (mScanFilter == null) { - iGatt.startScan(mLeHandle, false); + if (mAdvertiseCallback != null) { + iGatt.startAdvertising(mLeHandle); } else { - ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length]; - for(int i = 0; i != uuids.length; ++i) { - uuids[i] = new ParcelUuid(mScanFilter[i]); - } - iGatt.startScanWithUuids(mLeHandle, false, uuids); + if (mScanFilter == null) { + iGatt.startScan(mLeHandle, false); + } else { + ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length]; + for(int i = 0; i != uuids.length; ++i) { + uuids[i] = new ParcelUuid(mScanFilter[i]); + } + iGatt.startScanWithUuids(mLeHandle, false, uuids); + } } } else { Log.e(TAG, "onClientRegistered, BluetoothAdapter null"); @@ -1642,7 +1869,7 @@ public final class BluetoothAdapter { mLeHandle = -1; } if (mLeHandle == -1) { - // registration succeeded but start scan failed + // registration succeeded but start scan or advertise failed if (iGatt != null) { try { iGatt.unregisterClient(mLeHandle); @@ -1670,7 +1897,7 @@ public final class BluetoothAdapter { * @hide */ public void onScanResult(String address, int rssi, byte[] advData) { - if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi); + if (VDBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi); // Check null in case the scan has been stopped synchronized(this) { @@ -1759,9 +1986,33 @@ public final class BluetoothAdapter { // no op } - public void onListen(int status) { - // no op + public void onAdvertiseStateChange(int advertiseState, int status) { + Log.d(TAG, "on advertise call back, state: " + advertiseState + " status: " + status); + if (advertiseState == STATE_ADVERTISE_STARTED) { + mAdvertiseCallback.onAdvertiseStart(status); + } else { + synchronized (this) { + if (status == ADVERTISE_CALLBACK_SUCCESS) { + BluetoothAdapter adapter = mBluetoothAdapter.get(); + if (adapter != null) { + try { + IBluetoothGatt iGatt = + adapter.getBluetoothManager().getBluetoothGatt(); + Log.d(TAG, "unregistering client " + mLeHandle); + iGatt.unregisterClient(mLeHandle); + // Reset advertise app handle. + mLeHandle = -1; + adapter.mAdvertisingGattCallback = null; + } catch (RemoteException e) { + Log.e(TAG, "Failed to unregister client" + e); + } + } else { + Log.e(TAG, "cannot unregister client, BluetoothAdapter is null"); + } + } + } + mAdvertiseCallback.onAdvertiseStop(status); + } } } - } diff --git a/core/java/android/bluetooth/BluetoothAdvScanData.java b/core/java/android/bluetooth/BluetoothAdvScanData.java new file mode 100644 index 000000000000..df2c25629a3b --- /dev/null +++ b/core/java/android/bluetooth/BluetoothAdvScanData.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2013 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.bluetooth; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + + +/** + * This class provides the public APIs to set advertising and scan response data when BLE device + * operates in peripheral mode. <br> + * The exact format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11 + * @hide + */ +public final class BluetoothAdvScanData { + + /** + * Available data types of {@link BluetoothAdvScanData}. + */ + public static final int AD = 0; // Advertising Data + public static final int SCAN_RESPONSE = 1; // Scan Response + public static final int EIR = 2; // Extended Inquiry Response + + private static final String TAG = "BluetoothAdvScanData"; + + /** + * Data type of BluetoothAdvScanData. + */ + private final int mDataType; + /** + * Bluetooth Gatt Service. + */ + private IBluetoothGatt mBluetoothGatt; + + /** + * @param mBluetoothGatt + * @param dataType + */ + public BluetoothAdvScanData(IBluetoothGatt mBluetoothGatt, int dataType) { + this.mBluetoothGatt = mBluetoothGatt; + this.mDataType = dataType; + } + + /** + * @return advertising data type. + */ + public int getDataType() { + return mDataType; + } + + /** + * Set manufactureCode and manufactureData. + * Returns true if manufacturer data is set, false if there is no enough room to set + * manufacturer data or the data is already set. + * @param manufacturerCode - unique identifier for the manufacturer + * @param manufacturerData - data associated with the specific manufacturer. + */ + public boolean setManufacturerData(int manufacturerCode, byte[] manufacturerData) { + if (mDataType != AD) return false; + try { + return mBluetoothGatt.setAdvManufacturerCodeAndData(manufacturerCode, manufacturerData); + } catch (RemoteException e) { + Log.e(TAG, "Unable to set manufacturer id and data.", e); + return false; + } + } + + /** + * Set service data. Note the service data can only be set when the data type is {@code AD}; + * @param serviceData + */ + public boolean setServiceData(byte[] serviceData) { + + if (mDataType != AD) return false; + if (serviceData == null) return false; + try { + return mBluetoothGatt.setAdvServiceData(serviceData); + } catch (RemoteException e) { + Log.e(TAG, "Unable to set service data.", e); + return false; + } + } + + /** + * Returns an immutable list of service uuids that will be advertised. + */ + public List<ParcelUuid> getServiceUuids() { + try { + return Collections.unmodifiableList(mBluetoothGatt.getAdvServiceUuids()); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get service uuids.", e); + return null; + } + } + + /** + * Returns manufacturer data. + */ + public byte[] getManufacturerData() { + if (mBluetoothGatt == null) return null; + try { + return mBluetoothGatt.getAdvManufacturerData(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get manufacturer data.", e); + return null; + } + } + + /** + * Returns service data. + */ + public byte[] getServiceData() { + if (mBluetoothGatt == null) return null; + try { + return mBluetoothGatt.getAdvServiceData(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get service data.", e); + return null; + } + } + + /** + * Remove manufacturer data based on given manufacturer code. + * @param manufacturerCode + */ + public void removeManufacturerCodeAndData(int manufacturerCode) { + if (mBluetoothGatt != null) { + try { + mBluetoothGatt.removeAdvManufacturerCodeAndData(manufacturerCode); + } catch (RemoteException e) { + Log.e(TAG, "Unable to remove manufacturer : " + manufacturerCode, e); + } + } + } +} diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index a2bb78c4c158..ae6ad3b4f269 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -555,11 +555,12 @@ public final class BluetoothGatt implements BluetoothProfile { } /** - * Listen command status callback + * Advertise state change callback * @hide */ - public void onListen(int status) { - if (DBG) Log.d(TAG, "onListen() - status=" + status); + public void onAdvertiseStateChange(int state, int status) { + if (DBG) Log.d(TAG, "onAdvertiseStateChange() - state = " + + state + " status=" + status); } }; @@ -693,71 +694,6 @@ public final class BluetoothGatt implements BluetoothProfile { return true; } - /** - * Starts or stops sending of advertisement packages to listen for connection - * requests from a central devices. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * @param start Start or stop advertising - */ - /*package*/ void listen(boolean start) { - if (mContext == null || !mContext.getResources(). - getBoolean(com.android.internal.R.bool.config_bluetooth_le_peripheral_mode_supported)) { - throw new UnsupportedOperationException("BluetoothGatt#listen is blocked"); - } - if (DBG) Log.d(TAG, "listen() - start: " + start); - if (mService == null || mClientIf == 0) return; - - try { - mService.clientListen(mClientIf, start); - } catch (RemoteException e) { - Log.e(TAG,"",e); - } - } - - /** - * Sets the advertising data contained in the adv. response packet. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * @param advData true to set adv. data, false to set scan response - * @param includeName Inlucde the name in the adv. response - * @param includeTxPower Include TX power value - * @param minInterval Minimum desired scan interval (optional) - * @param maxInterval Maximum desired scan interval (optional) - * @param appearance The appearance flags for the device (optional) - * @param manufacturerData Manufacturer specific data including company ID (optional) - */ - /*package*/ void setAdvData(boolean advData, boolean includeName, boolean includeTxPower, - Integer minInterval, Integer maxInterval, - Integer appearance, Byte[] manufacturerData) { - if (mContext == null || !mContext.getResources(). - getBoolean(com.android.internal.R.bool.config_bluetooth_le_peripheral_mode_supported)) { - throw new UnsupportedOperationException("BluetoothGatt#setAdvData is blocked"); - } - if (DBG) Log.d(TAG, "setAdvData()"); - if (mService == null || mClientIf == 0) return; - - byte[] data = new byte[0]; - if (manufacturerData != null) { - data = new byte[manufacturerData.length]; - for(int i = 0; i != manufacturerData.length; ++i) { - data[i] = manufacturerData[i]; - } - } - - try { - mService.setAdvData(mClientIf, !advData, - includeName, includeTxPower, - minInterval != null ? minInterval : 0, - maxInterval != null ? maxInterval : 0, - appearance != null ? appearance : 0, data); - } catch (RemoteException e) { - Log.e(TAG,"",e); - } - } - /** * Disconnects an established connection, or cancels a connection attempt * currently in progress. diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java index 58ee54fdb100..153215cb5256 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -537,7 +537,7 @@ public final class BluetoothGattServer implements BluetoothProfile { try { mService.beginServiceDeclaration(mServerIf, service.getType(), service.getInstanceId(), service.getHandles(), - new ParcelUuid(service.getUuid())); + new ParcelUuid(service.getUuid()), service.isAdvertisePreferred()); List<BluetoothGattService> includedServices = service.getIncludedServices(); for (BluetoothGattService includedService : includedServices) { diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java index 1e6636960105..52bc0f796cb5 100644 --- a/core/java/android/bluetooth/BluetoothGattService.java +++ b/core/java/android/bluetooth/BluetoothGattService.java @@ -15,8 +15,6 @@ */ package android.bluetooth; -import android.bluetooth.BluetoothDevice; - import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -82,6 +80,11 @@ public class BluetoothGattService { protected List<BluetoothGattService> mIncludedServices; /** + * Whether the service uuid should be advertised. + */ + private boolean mAdvertisePreferred; + + /** * Create a new BluetoothGattService. * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * @@ -263,4 +266,20 @@ public class BluetoothGattService { } return null; } + + /** + * Returns whether the uuid of the service should be advertised. + * @hide + */ + public boolean isAdvertisePreferred() { + return mAdvertisePreferred; + } + + /** + * Set whether the service uuid should be advertised. + * @hide + */ + public void setAdvertisePreferred(boolean advertisePreferred) { + this.mAdvertisePreferred = advertisePreferred; + } } diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index abdf949ebcfd..4b28516e15a2 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -73,6 +73,9 @@ public final class BluetoothUuid { public static final ParcelUuid MAS = ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid BASE_UUID = + ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid[] RESERVED_UUIDS = { AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget, @@ -211,4 +214,17 @@ public final class BluetoothUuid { long value = (uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32; return (int)value; } + + /** + * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid. + * @param parcelUuid + * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise. + */ + public static boolean isShortUuid(ParcelUuid parcelUuid) { + UUID uuid = parcelUuid.getUuid(); + if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { + return false; + } + return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L); + } } diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index df393dbb2899..784cdcc5318c 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -37,10 +37,15 @@ interface IBluetoothGatt { void unregisterClient(in int clientIf); void clientConnect(in int clientIf, in String address, in boolean isDirect); void clientDisconnect(in int clientIf, in String address); - void clientListen(in int clientIf, in boolean start); - void setAdvData(in int clientIf, in boolean setScanRsp, in boolean inclName, - in boolean inclTxPower, in int minInterval, in int maxInterval, - in int appearance, in byte[] manufacturerData); + void startAdvertising(in int appIf); + void stopAdvertising(); + boolean setAdvServiceData(in byte[] serviceData); + byte[] getAdvServiceData(); + boolean setAdvManufacturerCodeAndData(int manufactureCode, in byte[] manufacturerData); + byte[] getAdvManufacturerData(); + List<ParcelUuid> getAdvServiceUuids(); + void removeAdvManufacturerCodeAndData(int manufacturerCode); + boolean isAdvertising(); void refreshDevice(in int clientIf, in String address); void discoverServices(in int clientIf, in String address); void readCharacteristic(in int clientIf, in String address, in int srvcType, @@ -75,7 +80,7 @@ interface IBluetoothGatt { void serverDisconnect(in int serverIf, in String address); void beginServiceDeclaration(in int serverIf, in int srvcType, in int srvcInstanceId, in int minHandles, - in ParcelUuid srvcId); + in ParcelUuid srvcId, boolean advertisePreferred); void addIncludedService(in int serverIf, in int srvcType, in int srvcInstanceId, in ParcelUuid srvcId); void addCharacteristic(in int serverIf, in ParcelUuid charId, diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl index 60c297b59798..7c69a066b465 100644 --- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl @@ -63,5 +63,5 @@ interface IBluetoothGattCallback { in int charInstId, in ParcelUuid charUuid, in byte[] value); void onReadRemoteRssi(in String address, in int rssi, in int status); - void onListen(in int status); + oneway void onAdvertiseStateChange(in int advertiseState, in int status); } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index b061fc5604ea..9d0ab3a58b5e 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -398,135 +398,137 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return AppOpsManager.MODE_ALLOWED; } - private void enforceReadPermissionInner(Uri uri) throws SecurityException { - final Context context = getContext(); - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - String missingPerm = null; - - if (UserHandle.isSameApp(uid, mMyUid)) { - return; + private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException { + enforceWritePermissionInner(uri); + if (mWriteOp != AppOpsManager.OP_NONE) { + return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg); } + return AppOpsManager.MODE_ALLOWED; + } + } - if (mExported) { - final String componentPerm = getReadPermission(); - if (componentPerm != null) { - if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { - return; - } else { - missingPerm = componentPerm; - } + /** {@hide} */ + protected void enforceReadPermissionInner(Uri uri) throws SecurityException { + final Context context = getContext(); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + String missingPerm = null; + + if (UserHandle.isSameApp(uid, mMyUid)) { + return; + } + + if (mExported) { + final String componentPerm = getReadPermission(); + if (componentPerm != null) { + if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { + return; + } else { + missingPerm = componentPerm; } + } - // track if unprotected read is allowed; any denied - // <path-permission> below removes this ability - boolean allowDefaultRead = (componentPerm == null); - - final PathPermission[] pps = getPathPermissions(); - if (pps != null) { - final String path = uri.getPath(); - for (PathPermission pp : pps) { - final String pathPerm = pp.getReadPermission(); - if (pathPerm != null && pp.match(path)) { - if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { - return; - } else { - // any denied <path-permission> means we lose - // default <provider> access. - allowDefaultRead = false; - missingPerm = pathPerm; - } + // track if unprotected read is allowed; any denied + // <path-permission> below removes this ability + boolean allowDefaultRead = (componentPerm == null); + + final PathPermission[] pps = getPathPermissions(); + if (pps != null) { + final String path = uri.getPath(); + for (PathPermission pp : pps) { + final String pathPerm = pp.getReadPermission(); + if (pathPerm != null && pp.match(path)) { + if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { + return; + } else { + // any denied <path-permission> means we lose + // default <provider> access. + allowDefaultRead = false; + missingPerm = pathPerm; } } } - - // if we passed <path-permission> checks above, and no default - // <provider> permission, then allow access. - if (allowDefaultRead) return; - } - - // last chance, check against any uri grants - if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) - == PERMISSION_GRANTED) { - return; } - final String failReason = mExported - ? " requires " + missingPerm + ", or grantUriPermission()" - : " requires the provider be exported, or grantUriPermission()"; - throw new SecurityException("Permission Denial: reading " - + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid - + ", uid=" + uid + failReason); + // if we passed <path-permission> checks above, and no default + // <provider> permission, then allow access. + if (allowDefaultRead) return; } - private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException { - enforceWritePermissionInner(uri); - if (mWriteOp != AppOpsManager.OP_NONE) { - return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg); - } - return AppOpsManager.MODE_ALLOWED; + // last chance, check against any uri grants + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) + == PERMISSION_GRANTED) { + return; } - private void enforceWritePermissionInner(Uri uri) throws SecurityException { - final Context context = getContext(); - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - String missingPerm = null; + final String failReason = mExported + ? " requires " + missingPerm + ", or grantUriPermission()" + : " requires the provider be exported, or grantUriPermission()"; + throw new SecurityException("Permission Denial: reading " + + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid + + ", uid=" + uid + failReason); + } - if (UserHandle.isSameApp(uid, mMyUid)) { - return; - } + /** {@hide} */ + protected void enforceWritePermissionInner(Uri uri) throws SecurityException { + final Context context = getContext(); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + String missingPerm = null; - if (mExported) { - final String componentPerm = getWritePermission(); - if (componentPerm != null) { - if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { - return; - } else { - missingPerm = componentPerm; - } + if (UserHandle.isSameApp(uid, mMyUid)) { + return; + } + + if (mExported) { + final String componentPerm = getWritePermission(); + if (componentPerm != null) { + if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { + return; + } else { + missingPerm = componentPerm; } + } - // track if unprotected write is allowed; any denied - // <path-permission> below removes this ability - boolean allowDefaultWrite = (componentPerm == null); - - final PathPermission[] pps = getPathPermissions(); - if (pps != null) { - final String path = uri.getPath(); - for (PathPermission pp : pps) { - final String pathPerm = pp.getWritePermission(); - if (pathPerm != null && pp.match(path)) { - if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { - return; - } else { - // any denied <path-permission> means we lose - // default <provider> access. - allowDefaultWrite = false; - missingPerm = pathPerm; - } + // track if unprotected write is allowed; any denied + // <path-permission> below removes this ability + boolean allowDefaultWrite = (componentPerm == null); + + final PathPermission[] pps = getPathPermissions(); + if (pps != null) { + final String path = uri.getPath(); + for (PathPermission pp : pps) { + final String pathPerm = pp.getWritePermission(); + if (pathPerm != null && pp.match(path)) { + if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { + return; + } else { + // any denied <path-permission> means we lose + // default <provider> access. + allowDefaultWrite = false; + missingPerm = pathPerm; } } } - - // if we passed <path-permission> checks above, and no default - // <provider> permission, then allow access. - if (allowDefaultWrite) return; } - // last chance, check against any uri grants - if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - == PERMISSION_GRANTED) { - return; - } + // if we passed <path-permission> checks above, and no default + // <provider> permission, then allow access. + if (allowDefaultWrite) return; + } - final String failReason = mExported - ? " requires " + missingPerm + ", or grantUriPermission()" - : " requires the provider be exported, or grantUriPermission()"; - throw new SecurityException("Permission Denial: writing " - + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid - + ", uid=" + uid + failReason); + // last chance, check against any uri grants + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + == PERMISSION_GRANTED) { + return; } + + final String failReason = mExported + ? " requires " + missingPerm + ", or grantUriPermission()" + : " requires the provider be exported, or grantUriPermission()"; + throw new SecurityException("Permission Denial: writing " + + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid + + ", uid=" + uid + failReason); } /** diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java index 0284882963c7..cffc653e44ef 100644 --- a/core/java/android/content/SyncInfo.java +++ b/core/java/android/content/SyncInfo.java @@ -55,6 +55,14 @@ public class SyncInfo implements Parcelable { } /** @hide */ + public SyncInfo(SyncInfo other) { + this.authorityId = other.authorityId; + this.account = new Account(other.account.name, other.account.type); + this.authority = other.authority; + this.startTime = other.startTime; + } + + /** @hide */ public int describeContents() { return 0; } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 267fb2af2dd1..20002ad9d471 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -53,6 +53,7 @@ import android.content.IntentSender; * {@hide} */ interface IPackageManager { + boolean isPackageAvailable(String packageName, int userId); PackageInfo getPackageInfo(String packageName, int flags, int userId); int getPackageUid(String packageName, int userId); int[] getPackageGids(String packageName); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 17d13e500e8e..e6da288fedbd 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -282,6 +282,10 @@ public class PackageParser { || (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; } + public static boolean isAvailable(PackageUserState state) { + return checkUseInstalledOrBlocked(0, state); + } + public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet<String> grantedPermissions, PackageUserState state, int userId) { diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 4fe2c4d3669b..a38beec44f93 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -334,6 +334,27 @@ public final class CameraCharacteristics extends CameraMetadata { /** * <p> + * If set to 1, the HAL will always split result + * metadata for a single capture into multiple buffers, + * returned using multiple process_capture_result calls. + * </p> + * <p> + * Does not need to be listed in static + * metadata. Support for partial results will be reworked in + * future versions of camera service. This quirk will stop + * working at that point; DO NOT USE without careful + * consideration of future support. + * </p> + * + * <b>Optional</b> - This value may be null on some devices. + * + * @hide + */ + public static final Key<Byte> QUIRKS_USE_PARTIAL_RESULT = + new Key<Byte>("android.quirks.usePartialResult", byte.class); + + /** + * <p> * How many output streams can be allocated at * the same time for each type of stream * </p> diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 7095e4d498ab..9e8d7d19e288 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -631,6 +631,36 @@ public interface CameraDevice extends AutoCloseable { } /** + * This method is called when some results from an image capture are + * available. + * + * <p>The result provided here will contain some subset of the fields of + * a full result. Multiple onCapturePartial calls may happen per + * capture; a given result field will only be present in one partial + * capture at most. The final onCaptureCompleted call will always + * contain all the fields, whether onCapturePartial was called or + * not.</p> + * + * <p>The default implementation of this method does nothing.</p> + * + * @param camera The CameraDevice sending the callback. + * @param request The request that was given to the CameraDevice + * @param result The partial output metadata from the capture, which + * includes a subset of the CaptureResult fields. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + * + * @hide + */ + public void onCapturePartial(CameraDevice camera, + CaptureRequest request, CaptureResult result) { + // default empty implementation + } + + /** * This method is called when an image capture has completed and the * result metadata is available. * diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index dbd0457e525f..535b9636769d 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -591,6 +591,32 @@ public final class CaptureResult extends CameraMetadata { /** * <p> + * Whether a result given to the framework is the + * final one for the capture, or only a partial that contains a + * subset of the full set of dynamic metadata + * values. + * </p> + * <p> + * The entries in the result metadata buffers for a + * single capture may not overlap, except for this entry. The + * FINAL buffers must retain FIFO ordering relative to the + * requests that generate them, so the FINAL buffer for frame 3 must + * always be sent to the framework after the FINAL buffer for frame 2, and + * before the FINAL buffer for frame 4. PARTIAL buffers may be returned + * in any order relative to other frames, but all PARTIAL buffers for a given + * capture must arrive before the FINAL buffer for that capture. This entry may + * only be used by the HAL if quirks.usePartialResult is set to 1. + * </p> + * + * <b>Optional</b> - This value may be null on some devices. + * + * @hide + */ + public static final Key<Boolean> QUIRKS_PARTIAL_RESULT = + new Key<Boolean>("android.quirks.partialResult", boolean.class); + + /** + * <p> * A frame counter set by the framework. This value monotonically * increases with every new result (that is, each new result has a unique * frameCount value). diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index c5d099921306..40586f0efcd9 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -19,27 +19,24 @@ package android.hardware.camera2.impl; import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraMetadata; -import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; -import android.os.IBinder; -import android.os.RemoteException; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import android.view.Surface; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; -import java.util.Stack; /** * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate @@ -49,6 +46,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final String TAG; private final boolean DEBUG; + private static final int REQUEST_ID_NONE = -1; + // TODO: guard every function with if (!mRemoteDevice) check (if it was closed) private ICameraDeviceUser mRemoteDevice; @@ -63,7 +62,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final SparseArray<CaptureListenerHolder> mCaptureListenerMap = new SparseArray<CaptureListenerHolder>(); - private final Stack<Integer> mRepeatingRequestIdStack = new Stack<Integer>(); + private int mRepeatingRequestId = REQUEST_ID_NONE; + private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>(); // Map stream IDs to Surfaces private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>(); @@ -186,7 +186,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { stopRepeating(); try { - mRemoteDevice.waitUntilIdle(); + waitUntilIdle(); // TODO: mRemoteDevice.beginConfigure // Delete all streams first (to free up HW resources) @@ -279,6 +279,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { checkIfCameraClosed(); int requestId; + if (repeating) { + stopRepeating(); + } + try { requestId = mRemoteDevice.submitRequest(request, repeating); } catch (CameraRuntimeException e) { @@ -293,7 +297,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } if (repeating) { - mRepeatingRequestIdStack.add(requestId); + mRepeatingRequestId = requestId; } if (mIdle) { @@ -327,8 +331,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { synchronized (mLock) { checkIfCameraClosed(); - while (!mRepeatingRequestIdStack.isEmpty()) { - int requestId = mRepeatingRequestIdStack.pop(); + if (mRepeatingRequestId != REQUEST_ID_NONE) { + + int requestId = mRepeatingRequestId; + mRepeatingRequestId = REQUEST_ID_NONE; + + // Queue for deletion after in-flight requests finish + mRepeatingRequestIdDeletedList.add(requestId); try { mRemoteDevice.cancelRequest(requestId); @@ -347,7 +356,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { synchronized (mLock) { checkIfCameraClosed(); - if (!mRepeatingRequestIdStack.isEmpty()) { + if (mRepeatingRequestId != REQUEST_ID_NONE) { throw new IllegalStateException("Active repeating request ongoing"); } @@ -359,6 +368,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // impossible return; } + + mRepeatingRequestId = REQUEST_ID_NONE; + mRepeatingRequestIdDeletedList.clear(); + mCaptureListenerMap.clear(); } } @@ -564,6 +577,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } final CaptureListenerHolder holder; + Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT); + boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial); + synchronized (mLock) { // TODO: move this whole map into this class to make it more testable, // exposing the methods necessary like subscribeToRequest, unsubscribe.. @@ -572,13 +588,28 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { holder = CameraDevice.this.mCaptureListenerMap.get(requestId); // Clean up listener once we no longer expect to see it. - - // TODO: how to handle repeating listeners? - // we probably want cancelRequest to return # of times it already enqueued and - // keep a counter. - if (holder != null && !holder.isRepeating()) { + if (holder != null && !holder.isRepeating() && !quirkIsPartialResult) { CameraDevice.this.mCaptureListenerMap.remove(requestId); } + + // TODO: add 'capture sequence completed' callback to the + // service, and clean up repeating requests there instead. + + // If we received a result for a repeating request and have + // prior repeating requests queued for deletion, remove those + // requests from mCaptureListenerMap. + if (holder != null && holder.isRepeating() && !quirkIsPartialResult + && mRepeatingRequestIdDeletedList.size() > 0) { + Iterator<Integer> iter = mRepeatingRequestIdDeletedList.iterator(); + while (iter.hasNext()) { + int deletedRequestId = iter.next(); + if (deletedRequestId < requestId) { + CameraDevice.this.mCaptureListenerMap.remove(deletedRequestId); + iter.remove(); + } + } + } + } // Check if we have a listener for this @@ -591,8 +622,25 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { final CaptureRequest request = holder.getRequest(); final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); - holder.getHandler().post( - new Runnable() { + Runnable resultDispatch = null; + + // Either send a partial result or the final capture completed result + if (quirkIsPartialResult) { + // Partial result + resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDevice.this.isClosed()){ + holder.getListener().onCapturePartial( + CameraDevice.this, + request, + resultAsCapture); + } + } + }; + } else { + // Final capture result + resultDispatch = new Runnable() { @Override public void run() { if (!CameraDevice.this.isClosed()){ @@ -602,7 +650,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { resultAsCapture); } } - }); + }; + } + + holder.getHandler().post(resultDispatch); } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index f12be5f404a9..093e0e9270ff 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -297,12 +297,31 @@ public final class DisplayManager { } /** - * Initiates a fresh scan of availble Wifi displays. + * Starts scanning for available Wifi displays. * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast. + * <p> + * Calls to this method nest and must be matched by an equal number of calls to + * {@link #stopWifiDisplayScan()}. + * </p><p> + * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}. + * </p> + * * @hide */ - public void scanWifiDisplays() { - mGlobal.scanWifiDisplays(); + public void startWifiDisplayScan() { + mGlobal.startWifiDisplayScan(); + } + + /** + * Stops scanning for available Wifi displays. + * <p> + * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}. + * </p> + * + * @hide + */ + public void stopWifiDisplayScan() { + mGlobal.stopWifiDisplayScan(); } /** @@ -312,8 +331,7 @@ public final class DisplayManager { * Automatically remembers the display after a successful connection, if not * already remembered. * </p><p> - * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY} to connect - * to unknown displays. No permissions are required to connect to already known displays. + * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}. * </p> * * @param deviceAddress The MAC address of the device to which we should connect. diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 936a0867fd83..34174301ac8e 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -72,6 +72,8 @@ public final class DisplayManagerGlobal { private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>(); private int[] mDisplayIdCache; + private int mWifiDisplayScanNestCount; + private DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; } @@ -267,11 +269,32 @@ public final class DisplayManagerGlobal { } } - public void scanWifiDisplays() { - try { - mDm.scanWifiDisplays(); - } catch (RemoteException ex) { - Log.e(TAG, "Failed to scan for Wifi displays.", ex); + public void startWifiDisplayScan() { + synchronized (mLock) { + if (mWifiDisplayScanNestCount++ == 0) { + registerCallbackIfNeededLocked(); + try { + mDm.startWifiDisplayScan(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to scan for Wifi displays.", ex); + } + } + } + } + + public void stopWifiDisplayScan() { + synchronized (mLock) { + if (--mWifiDisplayScanNestCount == 0) { + try { + mDm.stopWifiDisplayScan(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to scan for Wifi displays.", ex); + } + } else if (mWifiDisplayScanNestCount < 0) { + Log.wtf(TAG, "Wifi display scan nest count became negative: " + + mWifiDisplayScanNestCount); + mWifiDisplayScanNestCount = 0; + } } } diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 6b2c887639bd..68eb13fb6d58 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -29,11 +29,14 @@ interface IDisplayManager { void registerCallback(in IDisplayManagerCallback callback); - // No permissions required. - void scanWifiDisplays(); + // Requires CONFIGURE_WIFI_DISPLAY permission. + // The process must have previously registered a callback. + void startWifiDisplayScan(); - // Requires CONFIGURE_WIFI_DISPLAY permission to connect to an unknown device. - // No permissions required to connect to a known device. + // Requires CONFIGURE_WIFI_DISPLAY permission. + void stopWifiDisplayScan(); + + // Requires CONFIGURE_WIFI_DISPLAY permission. void connectWifiDisplay(String address); // No permissions required. @@ -45,6 +48,12 @@ interface IDisplayManager { // Requires CONFIGURE_WIFI_DISPLAY permission. void forgetWifiDisplay(String address); + // Requires CONFIGURE_WIFI_DISPLAY permission. + void pauseWifiDisplay(); + + // Requires CONFIGURE_WIFI_DISPLAY permission. + void resumeWifiDisplay(); + // No permissions required. WifiDisplayStatus getWifiDisplayStatus(); @@ -55,10 +64,4 @@ interface IDisplayManager { // No permissions required but must be same Uid as the creator. void releaseVirtualDisplay(in IBinder token); - - // Requires CONFIGURE_WIFI_DISPLAY permission. - void pauseWifiDisplay(); - - // Requires CONFIGURE_WIFI_DISPLAY permission. - void resumeWifiDisplay(); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index c78a973ceeb0..70c8750623fb 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -395,6 +395,8 @@ public class ConnectivityManager { private final IConnectivityManager mService; + private final String mPackageName; + /** * Tests if a given integer represents a valid network type. * @param networkType the type to be tested @@ -802,7 +804,7 @@ public class ConnectivityManager { public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { byte[] address = hostAddress.getAddress(); try { - return mService.requestRouteToHostAddress(networkType, address); + return mService.requestRouteToHostAddress(networkType, address, mPackageName); } catch (RemoteException e) { return false; } @@ -898,8 +900,9 @@ public class ConnectivityManager { /** * {@hide} */ - public ConnectivityManager(IConnectivityManager service) { + public ConnectivityManager(IConnectivityManager service, String packageName) { mService = checkNotNull(service, "missing IConnectivityManager"); + mPackageName = checkNotNull(packageName, "missing package name"); } /** {@hide} */ diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index c1da2e32aa00..4bca7fefbc44 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -71,9 +71,9 @@ interface IConnectivityManager int stopUsingNetworkFeature(int networkType, in String feature); - boolean requestRouteToHost(int networkType, int hostAddress); + boolean requestRouteToHost(int networkType, int hostAddress, String packageName); - boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress); + boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress, String packageName); boolean getMobileDataEnabled(); void setMobileDataEnabled(boolean enabled); diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java index b2ee50afc608..119e5330f127 100644 --- a/core/java/android/net/LocalSocketImpl.java +++ b/core/java/android/net/LocalSocketImpl.java @@ -326,6 +326,7 @@ class LocalSocketImpl } s.fd = accept(fd, s); + s.mFdCreatedInternally = true; } /** @@ -536,4 +537,3 @@ class LocalSocketImpl close(); } } - diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index c1065145dbf2..21352bf73f66 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -566,6 +566,17 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker { return false; } + + public void setInternalDataEnable(boolean enabled) { + if (DBG) log("setInternalDataEnable: E enabled=" + enabled); + final AsyncChannel channel = mDataConnectionTrackerAc; + if (channel != null) { + channel.sendMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE, + enabled ? DctConstants.ENABLED : DctConstants.DISABLED); + } + if (VDBG) log("setInternalDataEnable: X enabled=" + enabled); + } + @Override public void setUserDataEnable(boolean enabled) { if (DBG) log("setUserDataEnable: E enabled=" + enabled); diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java index b674324197ed..8a2c2b6c675f 100644 --- a/core/java/android/net/PacProxySelector.java +++ b/core/java/android/net/PacProxySelector.java @@ -97,7 +97,7 @@ public class PacProxySelector extends ProxySelector { } catch (Exception e) { port = 8080; } - ret.add(new Proxy(Type.HTTP, new InetSocketAddress(host, port))); + ret.add(new Proxy(Type.HTTP, InetSocketAddress.createUnresolved(host, port))); } } if (ret.size() == 0) { diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java index 78ac75f22c36..010e5277074c 100644 --- a/core/java/android/net/ProxyProperties.java +++ b/core/java/android/net/ProxyProperties.java @@ -139,6 +139,17 @@ public class ProxyProperties implements Parcelable { return false; } + public boolean isValid() { + if (!TextUtils.isEmpty(mPacFileUrl)) return true; + try { + Proxy.validate(mHost == null ? "" : mHost, mPort == 0 ? "" : Integer.toString(mPort), + mExclusionList == null ? "" : mExclusionList); + } catch (IllegalArgumentException e) { + return false; + } + return true; + } + public java.net.Proxy makeProxy() { java.net.Proxy proxy = java.net.Proxy.NO_PROXY; if (mHost != null) { diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index d7dc7f5eac11..7385dff3ff80 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -151,9 +151,10 @@ public class VpnService extends Service { } /** - * Protect a socket from VPN connections. The socket will be bound to the - * current default network interface, so its traffic will not be forwarded - * through VPN. This method is useful if some connections need to be kept + * Protect a socket from VPN connections. After protecting, data sent + * through this socket will go directly to the underlying network, + * so its traffic will not be forwarded through the VPN. + * This method is useful if some connections need to be kept * outside of VPN. For example, a VPN tunnel should protect itself if its * destination is covered by VPN routes. Otherwise its outgoing packets * will be sent back to the VPN interface and cause an infinite loop. This diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java index 9c3e405f44b7..7b2c62314089 100644 --- a/core/java/android/net/nsd/NsdManager.java +++ b/core/java/android/net/nsd/NsdManager.java @@ -301,27 +301,36 @@ public final class NsdManager { @Override public void handleMessage(Message message) { - Object listener = getListener(message.arg2); - boolean listenerRemove = true; switch (message.what) { case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); - break; + return; case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: mConnected.countDown(); - break; + return; case AsyncChannel.CMD_CHANNEL_DISCONNECTED: Log.e(TAG, "Channel lost"); + return; + default: break; + } + Object listener = getListener(message.arg2); + if (listener == null) { + Log.d(TAG, "Stale key " + message.arg2); + return; + } + boolean listenerRemove = true; + NsdServiceInfo ns = getNsdService(message.arg2); + switch (message.what) { case DISCOVER_SERVICES_STARTED: - String s = ((NsdServiceInfo) message.obj).getServiceType(); + String s = getNsdServiceInfoType((NsdServiceInfo) message.obj); ((DiscoveryListener) listener).onDiscoveryStarted(s); // Keep listener until stop discovery listenerRemove = false; break; case DISCOVER_SERVICES_FAILED: - ((DiscoveryListener) listener).onStartDiscoveryFailed( - getNsdService(message.arg2).getServiceType(), message.arg1); + ((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns), + message.arg1); break; case SERVICE_FOUND: ((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj); @@ -334,16 +343,14 @@ public final class NsdManager { listenerRemove = false; break; case STOP_DISCOVERY_FAILED: - ((DiscoveryListener) listener).onStopDiscoveryFailed( - getNsdService(message.arg2).getServiceType(), message.arg1); + ((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns), + message.arg1); break; case STOP_DISCOVERY_SUCCEEDED: - ((DiscoveryListener) listener).onDiscoveryStopped( - getNsdService(message.arg2).getServiceType()); + ((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns)); break; case REGISTER_SERVICE_FAILED: - ((RegistrationListener) listener).onRegistrationFailed( - getNsdService(message.arg2), message.arg1); + ((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1); break; case REGISTER_SERVICE_SUCCEEDED: ((RegistrationListener) listener).onServiceRegistered( @@ -352,16 +359,13 @@ public final class NsdManager { listenerRemove = false; break; case UNREGISTER_SERVICE_FAILED: - ((RegistrationListener) listener).onUnregistrationFailed( - getNsdService(message.arg2), message.arg1); + ((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1); break; case UNREGISTER_SERVICE_SUCCEEDED: - ((RegistrationListener) listener).onServiceUnregistered( - getNsdService(message.arg2)); + ((RegistrationListener) listener).onServiceUnregistered(ns); break; case RESOLVE_SERVICE_FAILED: - ((ResolveListener) listener).onResolveFailed( - getNsdService(message.arg2), message.arg1); + ((ResolveListener) listener).onResolveFailed(ns, message.arg1); break; case RESOLVE_SERVICE_SUCCEEDED: ((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj); @@ -421,6 +425,11 @@ public final class NsdManager { } + private String getNsdServiceInfoType(NsdServiceInfo s) { + if (s == null) return "?"; + return s.getServiceType(); + } + /** * Initialize AsyncChannel */ diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index dbaa325596d8..9ada6e640089 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -106,6 +106,11 @@ public abstract class BatteryStats implements Parcelable { public static final int FOREGROUND_ACTIVITY = 10; /** + * A constant indicating a wifi batched scan is active + */ + public static final int WIFI_BATCHED_SCAN = 11; + + /** * Include all of the data in the stats, including previously saved data. */ public static final int STATS_SINCE_CHARGED = 0; @@ -270,6 +275,8 @@ public abstract class BatteryStats implements Parcelable { public abstract void noteFullWifiLockReleasedLocked(); public abstract void noteWifiScanStartedLocked(); public abstract void noteWifiScanStoppedLocked(); + public abstract void noteWifiBatchedScanStartedLocked(int csph); + public abstract void noteWifiBatchedScanStoppedLocked(); public abstract void noteWifiMulticastEnabledLocked(); public abstract void noteWifiMulticastDisabledLocked(); public abstract void noteAudioTurnedOnLocked(); @@ -281,6 +288,7 @@ public abstract class BatteryStats implements Parcelable { public abstract long getWifiRunningTime(long batteryRealtime, int which); public abstract long getFullWifiLockTime(long batteryRealtime, int which); public abstract long getWifiScanTime(long batteryRealtime, int which); + public abstract long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which); public abstract long getWifiMulticastTime(long batteryRealtime, int which); public abstract long getAudioTurnedOnTime(long batteryRealtime, int which); @@ -288,6 +296,8 @@ public abstract class BatteryStats implements Parcelable { public abstract Timer getForegroundActivityTimer(); public abstract Timer getVibratorOnTimer(); + public static final int NUM_WIFI_BATCHED_SCAN_BINS = 5; + /** * Note that these must match the constants in android.os.PowerManager. * Also, if the user activity types change, the BatteryStatsImpl.VERSION must @@ -844,12 +854,13 @@ public abstract class BatteryStats implements Parcelable { public static final int DATA_CONNECTION_EVDO_B = 12; public static final int DATA_CONNECTION_LTE = 13; public static final int DATA_CONNECTION_EHRPD = 14; - public static final int DATA_CONNECTION_OTHER = 15; + public static final int DATA_CONNECTION_HSPAP = 15; + public static final int DATA_CONNECTION_OTHER = 16; static final String[] DATA_CONNECTION_NAMES = { "none", "gprs", "edge", "umts", "cdma", "evdo_0", "evdo_A", "1xrtt", "hsdpa", "hsupa", "hspa", "iden", "evdo_b", "lte", - "ehrpd", "other" + "ehrpd", "hspap", "other" }; public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER+1; @@ -2080,9 +2091,11 @@ public abstract class BatteryStats implements Parcelable { TimeUtils.formatDuration(ew.usedTime, pw); pw.print(" over "); TimeUtils.formatDuration(ew.overTime, pw); - pw.print(" ("); - pw.print((ew.usedTime*100)/ew.overTime); - pw.println("%)"); + if (ew.overTime != 0) { + pw.print(" ("); + pw.print((ew.usedTime*100)/ew.overTime); + pw.println("%)"); + } } } uidActivity = true; diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 21b8ae57e36a..f65b6ba79cd8 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -420,7 +420,7 @@ interface INetworkManagementService /** * Clear a user range from being associated with an interface. */ - void clearDnsInterfaceForUidRange(int uid_start, int uid_end); + void clearDnsInterfaceForUidRange(String iface, int uid_start, int uid_end); /** * Clear the mappings from pid to Dns interface and from uid range to Dns interface. diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 37a810236832..f7d1eb72f080 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -1069,11 +1069,11 @@ public class Preference implements Comparable<Preference> { * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>; * greater than 0 if this Preference sorts after <var>another</var>. */ + @Override public int compareTo(Preference another) { - if (mOrder != DEFAULT_ORDER - || (mOrder == DEFAULT_ORDER && another.mOrder != DEFAULT_ORDER)) { + if (mOrder != another.mOrder) { // Do order comparison - return mOrder - another.mOrder; + return mOrder - another.mOrder; } else if (mTitle == another.mTitle) { // If titles are null or share same object comparison return 0; diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl index 9d384fba874d..2b95c1244f79 100644 --- a/core/java/android/print/IPrintDocumentAdapter.aidl +++ b/core/java/android/print/IPrintDocumentAdapter.aidl @@ -37,4 +37,5 @@ oneway interface IPrintDocumentAdapter { void write(in PageRange[] pages, in ParcelFileDescriptor fd, IWriteResultCallback callback, int sequence); void finish(); + void cancel(); } diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java index 9e811a6b85f2..1f59bef853a3 100644 --- a/core/java/android/print/PrintDocumentAdapter.java +++ b/core/java/android/print/PrintDocumentAdapter.java @@ -141,15 +141,36 @@ public abstract class PrintDocumentAdapter { * or {@link LayoutResultCallback#onLayoutCancelled()} if layout was * cancelled in a response to a cancellation request via the passed in * {@link CancellationSignal}. Note that you <strong>must</strong> call one of - * the methods of the given callback for this method to be considered complete. + * the methods of the given callback for this method to be considered complete + * which is you will not receive any calls to this adapter until the current + * layout operation is complete by invoking a method on the callback instance. + * The callback methods can be invoked from an arbitrary thread. * </p> * <p> + * One of the arguments passed to this method is a {@link CancellationSignal} + * which is used to propagate requests from the system to your application for + * canceling the current layout operation. For example, a cancellation may be + * requested if the user changes a print option that may affect layout while + * you are performing a layout operation. In such a case the system will make + * an attempt to cancel the current layout as another one will have to be performed. + * Typically, you should register a cancellation callback in the cancellation + * signal. The cancellation callback <strong>will not</strong> be made on the + * main thread and can be registered as follows: + * </p> + * <pre> + * cancellationSignal.setOnCancelListener(new OnCancelListener() { + * @Override + * public void onCancel() { + * // Cancel layout + * } + * }); + * </pre> + * <p> * <strong>Note:</strong> If the content is large and a layout will be * performed, it is a good practice to schedule the work on a dedicated * thread and register an observer in the provided {@link * CancellationSignal} upon invocation of which you should stop the - * layout. The cancellation callback <strong>will not</strong> be made on - * the main thread. + * layout. * </p> * * @param oldAttributes The old print attributes. @@ -177,14 +198,36 @@ public abstract class PrintDocumentAdapter { * CharSequence)}, if an error occurred; or {@link WriteResultCallback#onWriteCancelled()}, * if writing was cancelled in a response to a cancellation request via the passed * in {@link CancellationSignal}. Note that you <strong>must</strong> call one of - * the methods of the given callback for this method to be considered complete. + * the methods of the given callback for this method to be considered complete which + * is you will not receive any calls to this adapter until the current write + * operation is complete by invoking a method on the callback instance. The callback + * methods can be invoked from an arbitrary thread. + * </p> + * <p> + * One of the arguments passed to this method is a {@link CancellationSignal} + * which is used to propagate requests from the system to your application for + * canceling the current write operation. For example, a cancellation may be + * requested if the user changes a print option that may affect layout while + * you are performing a write operation. In such a case the system will make + * an attempt to cancel the current write as a layout will have to be performed + * which then may be followed by a write. Typically, you should register a + * cancellation callback in the cancellation signal. The cancellation callback + * <strong>will not</strong> be made on the main thread and can be registered + * as follows: * </p> + * <pre> + * cancellationSignal.setOnCancelListener(new OnCancelListener() { + * @Override + * public void onCancel() { + * // Cancel write + * } + * }); + * </pre> * <p> * <strong>Note:</strong> If the printed content is large, it is a good * practice to schedule writing it on a dedicated thread and register an * observer in the provided {@link CancellationSignal} upon invocation of - * which you should stop writing. The cancellation callback will not be - * made on the main thread. + * which you should stop writing. * </p> * * @param pages The pages whose content to print - non-overlapping in ascending order. diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index bbfc3072b898..d1bb8fd20a4c 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -616,6 +616,18 @@ public final class PrintManager { } @Override + public void cancel() { + // Start not called or finish called or destroyed - nothing to do. + if (!mStartReqeusted || mFinishRequested || mDestroyed) { + return; + } + // Request cancellation of pending work if needed. + synchronized (mLock) { + cancelPreviousCancellableOperationLocked(); + } + } + + @Override public void onActivityPaused(Activity activity) { /* do nothing */ } @@ -848,6 +860,11 @@ public final class PrintManager { } final ILayoutResultCallback callback; synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } callback = mCallback; clearLocked(); } @@ -864,6 +881,11 @@ public final class PrintManager { public void onLayoutFailed(CharSequence error) { final ILayoutResultCallback callback; synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } callback = mCallback; clearLocked(); } @@ -879,6 +901,11 @@ public final class PrintManager { @Override public void onLayoutCancelled() { synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } clearLocked(); } } @@ -906,6 +933,11 @@ public final class PrintManager { public void onWriteFinished(PageRange[] pages) { final IWriteResultCallback callback; synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } callback = mCallback; clearLocked(); } @@ -928,6 +960,11 @@ public final class PrintManager { public void onWriteFailed(CharSequence error) { final IWriteResultCallback callback; synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } callback = mCallback; clearLocked(); } @@ -943,6 +980,11 @@ public final class PrintManager { @Override public void onWriteCancelled() { synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } clearLocked(); } } diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java index b615600d33ae..806a89d8481f 100644 --- a/core/java/android/print/PrinterCapabilitiesInfo.java +++ b/core/java/android/print/PrinterCapabilitiesInfo.java @@ -475,6 +475,12 @@ public final class PrinterCapabilitiesInfo implements Parcelable { * @param colorModes The color mode bit mask. * @param defaultColorMode The default color mode. * @return This builder. + * <p> + * <strong>Note:</strong> On platform version 19 (Kitkat) specifying + * only PrintAttributes#COLOR_MODE_MONOCHROME leads to a print spooler + * crash. Hence, you should declare either both color modes or + * PrintAttributes#COLOR_MODE_COLOR. + * </p> * * @throws IllegalArgumentException If color modes contains an invalid * mode bit or if the default color mode is invalid. diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index b16df28fbb3f..0863368408ba 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -18,6 +18,7 @@ package android.provider; import android.accounts.Account; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; import android.content.ContentResolver; @@ -40,6 +41,7 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Pair; import android.view.View; +import android.widget.Toast; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -7981,7 +7983,7 @@ public final class ContactsContract { // Trigger with obtained rectangle Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode, excludeMimes); - context.startActivity(intent); + startActivityWithErrorToast(context, intent); } /** @@ -8014,7 +8016,16 @@ public final class ContactsContract { String[] excludeMimes) { Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode, excludeMimes); - context.startActivity(intent); + startActivityWithErrorToast(context, intent); + } + + private static void startActivityWithErrorToast(Context context, Intent intent) { + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + Toast.makeText(context, com.android.internal.R.string.quick_contacts_not_available, + Toast.LENGTH_SHORT).show(); + } } } diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index e35b8eb42528..49816f8091b6 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -62,7 +62,8 @@ import java.io.FileNotFoundException; * android:authorities="com.example.mycloudprovider" * android:exported="true" * android:grantUriPermissions="true" - * android:permission="android.permission.MANAGE_DOCUMENTS"> + * android:permission="android.permission.MANAGE_DOCUMENTS" + * android:enabled="@bool/isAtLeastKitKat"> * <intent-filter> * <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> * </intent-filter> @@ -216,6 +217,8 @@ public abstract class DocumentsProvider extends ContentProvider { * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and * limited to only return the 64 most recently modified documents. + * <p> + * Recent documents do not support change notifications. * * @param projection list of {@link Document} columns to put into the * cursor. If {@code null} all supported columns should be @@ -250,7 +253,8 @@ public abstract class DocumentsProvider extends ContentProvider { * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that * you are still fetching additional data. Then, when the network data is * available, you can send a change notification to trigger a requery and - * return the complete contents. + * return the complete contents. To return a Cursor with extras, you need to + * extend and override {@link Cursor#getExtras()}. * <p> * To support change notifications, you must * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant @@ -360,7 +364,7 @@ public abstract class DocumentsProvider extends ContentProvider { * @param documentId the document to return. * @param mode the mode to open with, such as 'r', 'w', or 'rw'. * @param signal used by the caller to signal if the request should be - * cancelled. + * cancelled. May be null. * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler, * OnCloseListener) * @see ParcelFileDescriptor#createReliablePipe() @@ -384,7 +388,7 @@ public abstract class DocumentsProvider extends ContentProvider { * @param documentId the document to return. * @param sizeHint hint of the optimal thumbnail dimensions. * @param signal used by the caller to signal if the request should be - * cancelled. + * cancelled. May be null. * @see Document#FLAG_SUPPORTS_THUMBNAIL */ @SuppressWarnings("unused") @@ -512,10 +516,7 @@ public abstract class DocumentsProvider extends ContentProvider { final boolean callerHasManage = context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS) == PackageManager.PERMISSION_GRANTED; - if (!callerHasManage) { - getContext().enforceCallingOrSelfUriPermission( - documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method); - } + enforceWritePermissionInner(documentUri); final Bundle out = new Bundle(); try { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 04f3f0aad84d..3810eb32ca94 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5140,6 +5140,12 @@ public final class Settings { public static final String SMS_SHORT_CODE_RULE = "sms_short_code_rule"; /** + * Used to select TCP's default initial receiver window size in segments - defaults to a build config value + * @hide + */ + public static final String TCP_DEFAULT_INIT_RWND = "tcp_default_init_rwnd"; + + /** * Used to disable Tethering on a device - defaults to true * @hide */ diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index b8083632bc4f..2752085a427e 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -993,8 +993,16 @@ public class TextToSpeech { return runAction(new Action<Set<String>>() { @Override public Set<String> run(ITextToSpeechService service) throws RemoteException { - String[] features = service.getFeaturesForLanguage( + String[] features = null; + try { + features = service.getFeaturesForLanguage( locale.getISO3Language(), locale.getISO3Country(), locale.getVariant()); + } catch(MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 " + + "country code for locale: " + locale, e); + return null; + } + if (features != null) { final Set<String> featureSet = new HashSet<String>(); Collections.addAll(featureSet, features); diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java index 5fbd22e273e8..4f996cd7b91e 100644 --- a/core/java/android/speech/tts/TtsEngines.java +++ b/core/java/android/speech/tts/TtsEngines.java @@ -44,6 +44,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; +import java.util.MissingResourceException; /** * Support class for querying the list of available engines @@ -369,28 +370,34 @@ public class TtsEngines { public String getDefaultLocale() { final Locale locale = Locale.getDefault(); - // Note that the default locale might have an empty variant - // or language, and we take care that the construction is - // the same as {@link #getV1Locale} i.e no trailing delimiters - // or spaces. - String defaultLocale = locale.getISO3Language(); - if (TextUtils.isEmpty(defaultLocale)) { - Log.w(TAG, "Default locale is empty."); - return ""; - } + try { + // Note that the default locale might have an empty variant + // or language, and we take care that the construction is + // the same as {@link #getV1Locale} i.e no trailing delimiters + // or spaces. + String defaultLocale = locale.getISO3Language(); + if (TextUtils.isEmpty(defaultLocale)) { + Log.w(TAG, "Default locale is empty."); + return ""; + } + + if (!TextUtils.isEmpty(locale.getISO3Country())) { + defaultLocale += LOCALE_DELIMITER + locale.getISO3Country(); + } else { + // Do not allow locales of the form lang--variant with + // an empty country. + return defaultLocale; + } + if (!TextUtils.isEmpty(locale.getVariant())) { + defaultLocale += LOCALE_DELIMITER + locale.getVariant(); + } - if (!TextUtils.isEmpty(locale.getISO3Country())) { - defaultLocale += LOCALE_DELIMITER + locale.getISO3Country(); - } else { - // Do not allow locales of the form lang--variant with - // an empty country. return defaultLocale; + } catch (MissingResourceException e) { + // Default locale does not have a ISO 3166 and/or ISO 639-2/T codes. Return the + // default "eng-usa" (that would be the result of Locale.getDefault() == Locale.US). + return "eng-usa"; } - if (!TextUtils.isEmpty(locale.getVariant())) { - defaultLocale += LOCALE_DELIMITER + locale.getVariant(); - } - - return defaultLocale; } /** diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 160c630bf527..f839d52b0126 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -391,6 +391,15 @@ public class Html { out.append(">"); } else if (c == '&') { out.append("&"); + } else if (c >= 0xD800 && c <= 0xDFFF) { + if (c < 0xDC00 && i + 1 < end) { + char d = text.charAt(i + 1); + if (d >= 0xDC00 && d <= 0xDFFF) { + i++; + int codepoint = 0x010000 | (int) c - 0xD800 << 10 | (int) d - 0xDC00; + out.append("&#").append(codepoint).append(";"); + } + } } else if (c > 0x7E || c < ' ') { out.append("&#").append((int) c).append(";"); } else if (c == ' ') { diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index f76e1906604d..da9ba5a7fd75 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1255,7 +1255,8 @@ public abstract class Transition implements Cloneable { Animator anim = runningAnimators.keyAt(i); if (anim != null) { AnimationInfo oldInfo = runningAnimators.get(anim); - if (oldInfo != null) { + if (oldInfo != null && oldInfo.view != null && + oldInfo.view.getContext() == sceneRoot.getContext()) { boolean cancel = false; TransitionValues oldValues = oldInfo.values; View oldView = oldInfo.view; diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index 4af0f51a41e2..9f77d5ee7f9f 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -20,9 +20,7 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.util.ArrayMap; import android.util.AttributeSet; -import android.util.SparseArray; import android.util.Xml; import android.view.InflateException; import android.view.ViewGroup; @@ -43,15 +41,7 @@ import java.util.ArrayList; */ public class TransitionInflater { - // We only need one inflater for any given context. Also, this allows us to associate - // ids with unique instances per-Context, used to avoid re-inflating - // already-inflated resources into new/different instances - private static final ArrayMap<Context, TransitionInflater> sInflaterMap = - new ArrayMap<Context, TransitionInflater>(); - private Context mContext; - // TODO: do we need id maps for transitions and transitionMgrs as well? - SparseArray<Scene> mScenes = new SparseArray<Scene>(); private TransitionInflater(Context context) { mContext = context; @@ -61,13 +51,7 @@ public class TransitionInflater { * Obtains the TransitionInflater from the given context. */ public static TransitionInflater from(Context context) { - TransitionInflater inflater = sInflaterMap.get(context); - if (inflater != null) { - return inflater; - } - inflater = new TransitionInflater(context); - sInflaterMap.put(context, inflater); - return inflater; + return new TransitionInflater(context); } /** diff --git a/core/java/android/util/MapCollections.java b/core/java/android/util/MapCollections.java index f4a9b0bbb73e..28b788b97aa7 100644 --- a/core/java/android/util/MapCollections.java +++ b/core/java/android/util/MapCollections.java @@ -97,10 +97,10 @@ abstract class MapCollections<K, V> { if (!mEntryValid) { throw new IllegalStateException(); } + colRemoveAt(mIndex); mIndex--; mEnd--; mEntryValid = false; - colRemoveAt(mIndex); } @Override diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 354ea6613a39..7d310a254990 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -643,6 +643,15 @@ public final class Display { || uid == 0; } + /** + * Returns true if the display is a public presentation display. + * @hide + */ + public boolean isPublicPresentation() { + return (mFlags & (Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION)) == + Display.FLAG_PRESENTATION; + } + private void updateDisplayInfoLocked() { // Note: The display manager caches display info objects on our behalf. DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 1b76cb153ccc..c92a104b239d 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -78,7 +78,8 @@ interface IWindowManager void addWindowToken(IBinder token, int type); void removeWindowToken(IBinder token); void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId, - int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId); + int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, + int configChanges); void setAppGroupId(IBinder token, int groupId); void setAppOrientation(IApplicationToken token, int requestedOrientation); int getAppOrientation(IApplicationToken token); diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index f36c78fd4807..42a58a8efc22 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -323,6 +323,10 @@ public class ScaleGestureDetector { mInProgress = false; mInitialSpan = 0; mDoubleTapMode = DOUBLE_TAP_MODE_NONE; + } else if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS && streamComplete) { + mInProgress = false; + mInitialSpan = 0; + mDoubleTapMode = DOUBLE_TAP_MODE_NONE; } if (streamComplete) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 6c04c0b7aab1..5015b7e8d527 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -18,7 +18,6 @@ package android.view; import android.content.ClipData; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -3103,6 +3102,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int UNDEFINED_PADDING = Integer.MIN_VALUE; /** + * Cache if a left padding has been defined + */ + private boolean mLeftPaddingDefined = false; + + /** + * Cache if a right padding has been defined + */ + private boolean mRightPaddingDefined = false; + + /** * @hide */ int mOldWidthMeasureSpec = Integer.MIN_VALUE; @@ -3530,10 +3539,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int overScrollMode = mOverScrollMode; boolean initializeScrollbars = false; - boolean leftPaddingDefined = false; - boolean rightPaddingDefined = false; boolean startPaddingDefined = false; boolean endPaddingDefined = false; + boolean leftPaddingDefined = false; + boolean rightPaddingDefined = false; final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; @@ -3865,6 +3874,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setBackground(background); } + // setBackground above will record that padding is currently provided by the background. + // If we have padding specified via xml, record that here instead and use it. + mLeftPaddingDefined = leftPaddingDefined; + mRightPaddingDefined = rightPaddingDefined; + if (padding >= 0) { leftPadding = padding; topPadding = padding; @@ -3882,11 +3896,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if // defined. - if (!leftPaddingDefined && startPaddingDefined) { + if (!mLeftPaddingDefined && startPaddingDefined) { leftPadding = startPadding; } mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial; - if (!rightPaddingDefined && endPaddingDefined) { + if (!mRightPaddingDefined && endPaddingDefined) { rightPadding = endPadding; } mUserPaddingRightInitial = (rightPadding >= 0) ? rightPadding : mUserPaddingRightInitial; @@ -3898,10 +3912,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // defined. final boolean hasRelativePadding = startPaddingDefined || endPaddingDefined; - if (leftPaddingDefined && !hasRelativePadding) { + if (mLeftPaddingDefined && !hasRelativePadding) { mUserPaddingLeftInitial = leftPadding; } - if (rightPaddingDefined && !hasRelativePadding) { + if (mRightPaddingDefined && !hasRelativePadding) { mUserPaddingRightInitial = rightPadding; } } @@ -5900,6 +5914,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sThreadLocal.set(localInsets); } boolean res = computeFitSystemWindows(insets, localInsets); + mUserPaddingLeftInitial = localInsets.left; + mUserPaddingRightInitial = localInsets.right; internalSetPadding(localInsets.left, localInsets.top, localInsets.right, localInsets.bottom); return res; @@ -6527,7 +6543,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @attr ref android.R.styleable#View_filterTouchesWhenObscured */ public void setFilterTouchesWhenObscured(boolean enabled) { - setFlags(enabled ? 0 : FILTER_TOUCHES_WHEN_OBSCURED, + setFlags(enabled ? FILTER_TOUCHES_WHEN_OBSCURED : 0, FILTER_TOUCHES_WHEN_OBSCURED); } @@ -12133,12 +12149,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!isTextAlignmentResolved()) { resolveTextAlignment(); } - if (!isPaddingResolved()) { - resolvePadding(); - } + // Should resolve Drawables before Padding because we need the layout direction of the + // Drawable to correctly resolve Padding. if (!isDrawablesResolved()) { resolveDrawables(); } + if (!isPaddingResolved()) { + resolvePadding(); + } onRtlPropertiesChanged(getLayoutDirection()); return true; } @@ -12341,6 +12359,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // If start / end padding are defined, they will be resolved (hence overriding) to // left / right or right / left depending on the resolved layout direction. // If start / end padding are not defined, use the left / right ones. + if (mBackground != null && (!mLeftPaddingDefined || !mRightPaddingDefined)) { + Rect padding = sThreadLocal.get(); + if (padding == null) { + padding = new Rect(); + sThreadLocal.set(padding); + } + mBackground.getPadding(padding); + if (!mLeftPaddingDefined) { + mUserPaddingLeftInitial = padding.left; + } + if (!mRightPaddingDefined) { + mUserPaddingRightInitial = padding.right; + } + } switch (resolvedLayoutDirection) { case LAYOUT_DIRECTION_RTL: if (mUserPaddingStart != UNDEFINED_PADDING) { @@ -15336,6 +15368,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mUserPaddingRightInitial = padding.right; internalSetPadding(padding.left, padding.top, padding.right, padding.bottom); } + mLeftPaddingDefined = false; + mRightPaddingDefined = false; } // Compare the minimum sizes of the old Drawable and the new. If there isn't an old or @@ -15432,6 +15466,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mUserPaddingLeftInitial = left; mUserPaddingRightInitial = right; + mLeftPaddingDefined = true; + mRightPaddingDefined = true; + internalSetPadding(left, top, right, bottom); } @@ -15517,6 +15554,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mUserPaddingStart = start; mUserPaddingEnd = end; + mLeftPaddingDefined = true; + mRightPaddingDefined = true; switch(getLayoutDirection()) { case LAYOUT_DIRECTION_RTL: diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index c3f064fdfd4a..e67659c2485e 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -212,6 +212,14 @@ public class ViewConfiguration { */ private static final int OVERFLING_DISTANCE = 6; + /** + * Configuration values for overriding {@link #hasPermanentMenuKey()} behavior. + * These constants must match the definition in res/values/config.xml. + */ + private static final int HAS_PERMANENT_MENU_KEY_AUTODETECT = 0; + private static final int HAS_PERMANENT_MENU_KEY_TRUE = 1; + private static final int HAS_PERMANENT_MENU_KEY_FALSE = 2; + private final int mEdgeSlop; private final int mFadingEdgeLength; private final int mMinimumFlingVelocity; @@ -296,12 +304,31 @@ public class ViewConfiguration { mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f); if (!sHasPermanentMenuKeySet) { - IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - try { - sHasPermanentMenuKey = !wm.hasNavigationBar(); - sHasPermanentMenuKeySet = true; - } catch (RemoteException ex) { - sHasPermanentMenuKey = false; + final int configVal = res.getInteger( + com.android.internal.R.integer.config_overrideHasPermanentMenuKey); + + switch (configVal) { + default: + case HAS_PERMANENT_MENU_KEY_AUTODETECT: { + IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + try { + sHasPermanentMenuKey = !wm.hasNavigationBar(); + sHasPermanentMenuKeySet = true; + } catch (RemoteException ex) { + sHasPermanentMenuKey = false; + } + } + break; + + case HAS_PERMANENT_MENU_KEY_TRUE: + sHasPermanentMenuKey = true; + sHasPermanentMenuKeySet = true; + break; + + case HAS_PERMANENT_MENU_KEY_FALSE: + sHasPermanentMenuKey = false; + sHasPermanentMenuKeySet = true; + break; } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 637af6f54fd6..d779628571bf 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -232,8 +232,6 @@ public final class ViewRootImpl implements ViewParent, InputStage mFirstInputStage; InputStage mFirstPostImeInputStage; - boolean mFlipControllerFallbackKeys; - boolean mWindowAttributesChanged = false; int mWindowAttributesChangesFlag = 0; @@ -370,8 +368,6 @@ public final class ViewRootImpl implements ViewParent, mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context); mChoreographer = Choreographer.getInstance(); - mFlipControllerFallbackKeys = - context.getResources().getBoolean(R.bool.flip_controller_fallback_keys); PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mAttachInfo.mScreenOn = powerManager.isScreenOn(); @@ -2912,11 +2908,8 @@ public final class ViewRootImpl implements ViewParent, mView.dispatchConfigurationChanged(config); } } - - mFlipControllerFallbackKeys = - mContext.getResources().getBoolean(R.bool.flip_controller_fallback_keys); } - + /** * Return true if child is an ancestor of parent, (or equal to the parent). */ @@ -3393,16 +3386,7 @@ public final class ViewRootImpl implements ViewParent, public final void deliver(QueuedInputEvent q) { if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) { forward(q); - } else if (mView == null || !mAdded) { - Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent); - finish(q, false); - } else if (!mAttachInfo.mHasWindowFocus && - !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && - !isTerminalInputEvent(q.mEvent)) { - // If this is a focused event and the window doesn't currently have input focus, - // then drop this event. This could be an event that came back from the previous - // stage but the window has lost focus in the meantime. - Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); + } else if (shouldDropInputEvent(q)) { finish(q, false); } else { apply(q, onProcess(q)); @@ -3461,6 +3445,22 @@ public final class ViewRootImpl implements ViewParent, } } + protected boolean shouldDropInputEvent(QueuedInputEvent q) { + if (mView == null || !mAdded) { + Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent); + return true; + } else if (!mAttachInfo.mHasWindowFocus && + !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && + !isTerminalInputEvent(q.mEvent)) { + // If this is a focused event and the window doesn't currently have input focus, + // then drop this event. This could be an event that came back from the previous + // stage but the window has lost focus in the meantime. + Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); + return true; + } + return false; + } + void dump(String prefix, PrintWriter writer) { if (mNext != null) { mNext.dump(prefix, writer); @@ -3846,6 +3846,10 @@ public final class ViewRootImpl implements ViewParent, return FINISH_HANDLED; } + if (shouldDropInputEvent(q)) { + return FINISH_NOT_HANDLED; + } + // If the Control modifier is held, try to interpret the key as a shortcut. if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed() @@ -3854,12 +3858,18 @@ public final class ViewRootImpl implements ViewParent, if (mView.dispatchKeyShortcutEvent(event)) { return FINISH_HANDLED; } + if (shouldDropInputEvent(q)) { + return FINISH_NOT_HANDLED; + } } // Apply the fallback event policy. if (mFallbackEventHandler.dispatchKeyEvent(event)) { return FINISH_HANDLED; } + if (shouldDropInputEvent(q)) { + return FINISH_NOT_HANDLED; + } // Handle automatic focus changes. if (event.getAction() == KeyEvent.ACTION_DOWN) { @@ -3968,7 +3978,6 @@ public final class ViewRootImpl implements ViewParent, private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler(); private final SyntheticTouchNavigationHandler mTouchNavigation = new SyntheticTouchNavigationHandler(); - private final SyntheticKeyHandler mKeys = new SyntheticKeyHandler(); public SyntheticInputStage() { super(null); @@ -3991,12 +4000,7 @@ public final class ViewRootImpl implements ViewParent, mTouchNavigation.process(event); return FINISH_HANDLED; } - } else if (q.mEvent instanceof KeyEvent) { - if (mKeys.process((KeyEvent) q.mEvent)) { - return FINISH_HANDLED; - } } - return FORWARD; } @@ -4826,63 +4830,6 @@ public final class ViewRootImpl implements ViewParent, }; } - final class SyntheticKeyHandler { - - public boolean process(KeyEvent event) { - // In some locales (like Japan) controllers use B for confirm and A for back, rather - // than vice versa, so we need to special case this here since the input system itself - // is not locale-aware. - int keyCode; - switch(event.getKeyCode()) { - case KeyEvent.KEYCODE_BUTTON_A: - case KeyEvent.KEYCODE_BUTTON_C: - case KeyEvent.KEYCODE_BUTTON_X: - case KeyEvent.KEYCODE_BUTTON_Z: - keyCode = mFlipControllerFallbackKeys ? - KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_DPAD_CENTER; - break; - case KeyEvent.KEYCODE_BUTTON_B: - case KeyEvent.KEYCODE_BUTTON_Y: - keyCode = mFlipControllerFallbackKeys ? - KeyEvent.KEYCODE_DPAD_CENTER : KeyEvent.KEYCODE_BACK; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBL: - case KeyEvent.KEYCODE_BUTTON_THUMBR: - case KeyEvent.KEYCODE_BUTTON_START: - case KeyEvent.KEYCODE_BUTTON_1: - case KeyEvent.KEYCODE_BUTTON_2: - case KeyEvent.KEYCODE_BUTTON_3: - case KeyEvent.KEYCODE_BUTTON_4: - case KeyEvent.KEYCODE_BUTTON_5: - case KeyEvent.KEYCODE_BUTTON_6: - case KeyEvent.KEYCODE_BUTTON_7: - case KeyEvent.KEYCODE_BUTTON_8: - case KeyEvent.KEYCODE_BUTTON_9: - case KeyEvent.KEYCODE_BUTTON_10: - case KeyEvent.KEYCODE_BUTTON_11: - case KeyEvent.KEYCODE_BUTTON_12: - case KeyEvent.KEYCODE_BUTTON_13: - case KeyEvent.KEYCODE_BUTTON_14: - case KeyEvent.KEYCODE_BUTTON_15: - case KeyEvent.KEYCODE_BUTTON_16: - keyCode = KeyEvent.KEYCODE_DPAD_CENTER; - break; - case KeyEvent.KEYCODE_BUTTON_SELECT: - case KeyEvent.KEYCODE_BUTTON_MODE: - keyCode = KeyEvent.KEYCODE_MENU; - default: - return false; - } - - enqueueInputEvent(new KeyEvent(event.getDownTime(), event.getEventTime(), - event.getAction(), keyCode, event.getRepeatCount(), event.getMetaState(), - event.getDeviceId(), event.getScanCode(), - event.getFlags() | KeyEvent.FLAG_FALLBACK, event.getSource())); - return true; - } - - } - /** * Returns true if the key is used for keyboard navigation. * @param keyEvent The key event. diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java index f0e6677d016b..52f9c0b480ca 100644 --- a/core/java/android/view/VolumePanel.java +++ b/core/java/android/view/VolumePanel.java @@ -311,7 +311,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie lp.type = LayoutParams.TYPE_VOLUME_OVERLAY; lp.width = LayoutParams.WRAP_CONTENT; lp.height = LayoutParams.WRAP_CONTENT; - lp.privateFlags |= LayoutParams.PRIVATE_FLAG_FORCE_SHOW_NAV_BAR; window.setAttributes(lp); window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 0ce4da5c4dc5..53a4c0d0df51 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1063,13 +1063,6 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010; /** - * Special flag for the volume overlay: force the window manager out of "hide nav bar" - * mode while the window is on screen. - * - * {@hide} */ - public static final int PRIVATE_FLAG_FORCE_SHOW_NAV_BAR = 0x00000020; - - /** * Never animate position changes of the window. * * {@hide} */ diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index 40f95cea1d18..2ab30246662c 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -534,6 +534,13 @@ public final class InputMethodSubtype implements Parcelable { private static int hashCodeInternal(String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable) { + // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new + // attribute is added in order to avoid enabled subtypes being unexpectedly disabled. + final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable; + if (needsToCalculateCompatibleHashCode) { + return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary, + overridesImplicitlyEnabledSubtype}); + } return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 5bc39f158408..d53bb74c81ab 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -686,6 +686,15 @@ public class WebView extends AbsoluteLayout } /** + * Used only by internal tests to free up memory. + * + * @hide + */ + public static void freeMemoryForTests() { + getFactory().getStatics().freeMemoryForTests(); + } + + /** * Informs WebView of the network state. This is used to set * the JavaScript property window.navigator.isOnline and * generates the online/offline event as specified in HTML5, sec. 5.7.7 diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index 9d9d88292c1a..e391aafd5465 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -50,6 +50,11 @@ public interface WebViewFactoryProvider { String getDefaultUserAgent(Context context); /** + * Used for tests only. + */ + void freeMemoryForTests(); + + /** * Implements the API method: * {@link android.webkit.WebView#setWebContentsDebuggingEnabled(boolean) } */ diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 6a7a34fb8ada..25a43a6a2969 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2076,22 +2076,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mInLayout = true; + final int childCount = getChildCount(); if (changed) { - int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } mRecycler.markChildrenDirty(); } - if (mFastScroller != null && (mItemCount != mOldItemCount || mDataChanged)) { - mFastScroller.onItemCountChanged(mItemCount); - } - layoutChildren(); mInLayout = false; mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; + + // TODO: Move somewhere sane. This doesn't belong in onLayout(). + if (mFastScroller != null) { + mFastScroller.onItemCountChanged(getChildCount(), mItemCount); + } } /** @@ -2228,26 +2229,34 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); isScrap[0] = false; - View scrapView; - scrapView = mRecycler.getTransientStateView(position); - if (scrapView == null) { - scrapView = mRecycler.getScrapView(position); - } + // Check whether we have a transient state view. Attempt to re-bind the + // data and discard the view if we fail. + final View transientView = mRecycler.getTransientStateView(position); + if (transientView != null) { + final LayoutParams params = (LayoutParams) transientView.getLayoutParams(); - View child; - if (scrapView != null) { - child = mAdapter.getView(position, scrapView, this); + // If the view type hasn't changed, attempt to re-bind the data. + if (params.viewType == mAdapter.getItemViewType(position)) { + final View updatedView = mAdapter.getView(position, transientView, this); - if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + // If we failed to re-bind the data, scrap the obtained view. + if (updatedView != transientView) { + mRecycler.addScrapView(updatedView, position); + } } + // Scrap view implies temporary detachment. + isScrap[0] = true; + return transientView; + } + + final View scrapView = mRecycler.getScrapView(position); + final View child = mAdapter.getView(position, scrapView, this); + if (scrapView != null) { if (child != scrapView) { + // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position); - if (mCacheColorHint != 0) { - child.setDrawingCacheBackgroundColor(mCacheColorHint); - } } else { isScrap[0] = true; @@ -2259,16 +2268,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te child.dispatchFinishTemporaryDetach(); } - } else { - child = mAdapter.getView(position, null, this); + } - if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { - child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - } + if (mCacheColorHint != 0) { + child.setDrawingCacheBackgroundColor(mCacheColorHint); + } - if (mCacheColorHint != 0) { - child.setDrawingCacheBackgroundColor(mCacheColorHint); - } + if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } if (mAdapterHasStableIds) { @@ -6562,12 +6569,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } } - if (mTransientStateViews != null) { - mTransientStateViews.clear(); - } - if (mTransientStateViewsById != null) { - mTransientStateViewsById.clear(); - } + + clearTransientStateViews(); } /** @@ -6634,14 +6637,26 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** - * Dump any currently saved views with transient state. + * Dumps and fully detaches any currently saved views with transient + * state. */ void clearTransientStateViews() { - if (mTransientStateViews != null) { - mTransientStateViews.clear(); + final SparseArray<View> viewsByPos = mTransientStateViews; + if (viewsByPos != null) { + final int N = viewsByPos.size(); + for (int i = 0; i < N; i++) { + removeDetachedView(viewsByPos.valueAt(i), false); + } + viewsByPos.clear(); } - if (mTransientStateViewsById != null) { - mTransientStateViewsById.clear(); + + final LongSparseArray<View> viewsById = mTransientStateViewsById; + if (viewsById != null) { + final int N = viewsById.size(); + for (int i = 0; i < N; i++) { + removeDetachedView(viewsById.valueAt(i), false); + } + viewsById.clear(); } } @@ -6686,6 +6701,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te scrap.dispatchStartTemporaryDetach(); + // The the accessibility state of the view may change while temporary + // detached and we do not allow detached views to fire accessibility + // events. So we are announcing that the subtree changed giving a chance + // to clients holding on to a view in this subtree to refresh it. + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { @@ -6759,44 +6781,48 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (victim != null) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) victim.getLayoutParams(); - int whichScrap = lp.viewType; + final int whichScrap = lp.viewType; activeViews[i] = null; - final boolean scrapHasTransientState = victim.hasTransientState(); - if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) { - // Do not move views that should be ignored - if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER && - scrapHasTransientState) { + if (victim.hasTransientState()) { + // Store views with transient state for later use. + victim.dispatchStartTemporaryDetach(); + + if (mAdapter != null && mAdapterHasStableIds) { + if (mTransientStateViewsById == null) { + mTransientStateViewsById = new LongSparseArray<View>(); + } + long id = mAdapter.getItemId(mFirstActivePosition + i); + mTransientStateViewsById.put(id, victim); + } else if (!mDataChanged) { + if (mTransientStateViews == null) { + mTransientStateViews = new SparseArray<View>(); + } + mTransientStateViews.put(mFirstActivePosition + i, victim); + } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { + // The data has changed, we can't keep this view. removeDetachedView(victim, false); } - if (scrapHasTransientState) { - if (mAdapter != null && mAdapterHasStableIds) { - if (mTransientStateViewsById == null) { - mTransientStateViewsById = new LongSparseArray<View>(); - } - long id = mAdapter.getItemId(mFirstActivePosition + i); - mTransientStateViewsById.put(id, victim); - } else { - if (mTransientStateViews == null) { - mTransientStateViews = new SparseArray<View>(); - } - mTransientStateViews.put(mFirstActivePosition + i, victim); - } + } else if (!shouldRecycleViewType(whichScrap)) { + // Discard non-recyclable views except headers/footers. + if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { + removeDetachedView(victim, false); + } + } else { + // Store everything else on the appropriate scrap heap. + if (multipleScraps) { + scrapViews = mScrapViews[whichScrap]; } - continue; - } - if (multipleScraps) { - scrapViews = mScrapViews[whichScrap]; - } - victim.dispatchStartTemporaryDetach(); - lp.scrappedFromPosition = mFirstActivePosition + i; - scrapViews.add(victim); + victim.dispatchStartTemporaryDetach(); + lp.scrappedFromPosition = mFirstActivePosition + i; + scrapViews.add(victim); - victim.setAccessibilityDelegate(null); - if (hasListener) { - mRecyclerListener.onMovedToScrapHeap(victim); + victim.setAccessibilityDelegate(null); + if (hasListener) { + mRecyclerListener.onMovedToScrapHeap(victim); + } } } } @@ -6805,8 +6831,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** - * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews. - * (This can happen if an adapter does not recycle its views). + * Makes sure that the size of mScrapViews does not exceed the size of + * mActiveViews, which can happen if an adapter does not recycle its + * views. Removes cached transient state views that no longer have + * transient state. */ private void pruneScrapViews() { final int maxViews = mActiveViews.length; @@ -6822,20 +6850,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } - if (mTransientStateViews != null) { - for (int i = 0; i < mTransientStateViews.size(); i++) { - final View v = mTransientStateViews.valueAt(i); + final SparseArray<View> transViewsByPos = mTransientStateViews; + if (transViewsByPos != null) { + for (int i = 0; i < transViewsByPos.size(); i++) { + final View v = transViewsByPos.valueAt(i); if (!v.hasTransientState()) { - mTransientStateViews.removeAt(i); + removeDetachedView(v, false); + transViewsByPos.removeAt(i); i--; } } } - if (mTransientStateViewsById != null) { - for (int i = 0; i < mTransientStateViewsById.size(); i++) { - final View v = mTransientStateViewsById.valueAt(i); + + final LongSparseArray<View> transViewsById = mTransientStateViewsById; + if (transViewsById != null) { + for (int i = 0; i < transViewsById.size(); i++) { + final View v = transViewsById.valueAt(i); if (!v.hasTransientState()) { - mTransientStateViewsById.removeAt(i); + removeDetachedView(v, false); + transViewsById.removeAt(i); i--; } } diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index 0957ab4670e6..e90b4608a888 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -1210,35 +1210,38 @@ public class CalendarView extends FrameLayout { child = (WeekView) view.getChildAt(offset); } - // Find out which month we're moving into - int month; - if (mIsScrollingUp) { - month = child.getMonthOfFirstWeekDay(); - } else { - month = child.getMonthOfLastWeekDay(); - } - - // And how it relates to our current highlighted month - int monthDiff; - if (mCurrentMonthDisplayed == 11 && month == 0) { - monthDiff = 1; - } else if (mCurrentMonthDisplayed == 0 && month == 11) { - monthDiff = -1; - } else { - monthDiff = month - mCurrentMonthDisplayed; - } - - // Only switch months if we're scrolling away from the currently - // selected month - if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { - Calendar firstDay = child.getFirstDay(); + if (child != null) { + // Find out which month we're moving into + int month; if (mIsScrollingUp) { - firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); + month = child.getMonthOfFirstWeekDay(); } else { - firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); + month = child.getMonthOfLastWeekDay(); + } + + // And how it relates to our current highlighted month + int monthDiff; + if (mCurrentMonthDisplayed == 11 && month == 0) { + monthDiff = 1; + } else if (mCurrentMonthDisplayed == 0 && month == 11) { + monthDiff = -1; + } else { + monthDiff = month - mCurrentMonthDisplayed; + } + + // Only switch months if we're scrolling away from the currently + // selected month + if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { + Calendar firstDay = child.getFirstDay(); + if (mIsScrollingUp) { + firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); + } else { + firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); + } + setMonthDisplayed(firstDay); } - setMonthDisplayed(firstDay); } + mPreviousScrollPosition = currScroll; mPreviousScrollState = mCurrentScrollState; } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 9dab7b4b8c41..748af7b63dd2 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -317,7 +317,7 @@ public class Editor { mErrorPopup = null; } - + mShowErrorAfterAttach = false; } else { setErrorIcon(icon); if (mTextView.isFocused()) { diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 01ac8fdba6d0..4379bf62336f 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -224,6 +224,8 @@ class FastScroller { mHasPendingDrag = false; } }; + private int mOldItemCount; + private int mOldChildCount; /** * Used to delay hiding fast scroll decorations. @@ -248,6 +250,8 @@ class FastScroller { public FastScroller(AbsListView listView) { mList = listView; mOverlay = listView.getOverlay(); + mOldItemCount = listView.getCount(); + mOldChildCount = listView.getChildCount(); final Context context = listView.getContext(); mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); @@ -258,6 +262,7 @@ class FastScroller { final ImageView trackImage = new ImageView(context); mTrackImage = trackImage; + updateLongList(mOldChildCount, mOldItemCount); int width = 0; // Add track to overlay if it has an image. @@ -445,20 +450,23 @@ class FastScroller { updateLayout(); } - public void onItemCountChanged(int totalItemCount) { - final int visibleItemCount = mList.getChildCount(); - final boolean hasMoreItems = totalItemCount - visibleItemCount > 0; - if (hasMoreItems && mState != STATE_DRAGGING) { - final int firstVisibleItem = mList.getFirstVisiblePosition(); - setThumbPos(getPosFromItemCount(firstVisibleItem, visibleItemCount, totalItemCount)); - } + public void onItemCountChanged(int childCount, int itemCount) { + if (mOldItemCount != itemCount || mOldChildCount != childCount) { + mOldItemCount = itemCount; + mOldChildCount = childCount; - updateLongList(visibleItemCount, totalItemCount); + final boolean hasMoreItems = itemCount - childCount > 0; + if (hasMoreItems && mState != STATE_DRAGGING) { + final int firstVisibleItem = mList.getFirstVisiblePosition(); + setThumbPos(getPosFromItemCount(firstVisibleItem, childCount, itemCount)); + } + + updateLongList(childCount, itemCount); + } } - private void updateLongList(int visibleItemCount, int totalItemCount) { - final boolean longList = visibleItemCount > 0 - && totalItemCount / visibleItemCount >= MIN_PAGES; + private void updateLongList(int childCount, int itemCount) { + final boolean longList = childCount > 0 && itemCount / childCount >= MIN_PAGES; if (mLongList != longList) { mLongList = longList; diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 313dd54cde19..f05179bb5883 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -710,6 +710,7 @@ public class ImageView extends View { } d.setLevel(mLevel); d.setLayoutDirection(getLayoutDirection()); + d.setVisible(getVisibility() == VISIBLE, true); mDrawableWidth = d.getIntrinsicWidth(); mDrawableHeight = d.getIntrinsicHeight(); applyColorMod(); diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 65a2d4d2d02d..6a369a60a324 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -308,6 +308,11 @@ public class ProgressBar extends View { mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl); a.recycle(); + + // If not explicitly specified this view is important for accessibility. + if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } } /** @@ -959,9 +964,11 @@ public class ProgressBar extends View { if (!mInDrawing) { if (verifyDrawable(dr)) { final Rect dirty = dr.getBounds(); + final int scrollX = mScrollX + mPaddingLeft; + final int scrollY = mScrollY + mPaddingTop; - invalidate(dirty.left + mScrollX, dirty.top + mScrollY, - dirty.right + mScrollX, dirty.bottom + mScrollY); + invalidate(dirty.left + scrollX, dirty.top + scrollY, + dirty.right + scrollX, dirty.bottom + scrollY); } else { super.invalidateDrawable(dr); } diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index b204dfd0c032..1cda631a498a 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -731,10 +731,14 @@ public class SpellChecker implements SpellCheckerSessionListener { } } - if (scheduleOtherSpellCheck) { + if (scheduleOtherSpellCheck && wordStart <= end) { // Update range span: start new spell check from last wordStart setRangeSpan(editable, wordStart, end); } else { + if (DBG && scheduleOtherSpellCheck) { + Log.w(TAG, "Trying to schedule spellcheck for invalid region, from " + + wordStart + " to " + end); + } removeRangeSpan(editable); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index cb930d6fbcb2..37121e210fd0 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -56,6 +56,7 @@ import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; import android.text.SpannableString; +import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.StaticLayout; @@ -3494,19 +3495,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ss.selEnd = end; if (mText instanceof Spanned) { - /* - * Calling setText() strips off any ChangeWatchers; - * strip them now to avoid leaking references. - * But do it to a copy so that if there are any - * further changes to the text of this view, it - * won't get into an inconsistent state. - */ - - Spannable sp = new SpannableString(mText); - - for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) { - sp.removeSpan(cw); - } + Spannable sp = new SpannableStringBuilder(mText); if (mEditor != null) { removeMisspelledSpans(sp); @@ -8678,6 +8667,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener super.onRtlPropertiesChanged(layoutDirection); mTextDir = getTextDirectionHeuristic(); + + if (mLayout != null) { + checkForRelayout(); + } } TextDirectionHeuristic getTextDirectionHeuristic() { diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index fbdf318752af..d57b7391c4d0 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -56,7 +56,17 @@ import java.util.Vector; * can load images from various sources (such as resources or content * providers), takes care of computing its measurement from the video so that * it can be used in any layout manager, and provides various display options - * such as scaling and tinting. + * such as scaling and tinting.<p> + * + * <em>Note: VideoView does not retain its full state when going into the + * background.</em> In particular, it does not restore the current play state, + * play position, selected tracks, or any subtitle tracks added via + * {@link #addSubtitleSource addSubtitleSource()}. Applications should + * save and restore these on their own in + * {@link android.app.Activity#onSaveInstanceState} and + * {@link android.app.Activity#onRestoreInstanceState}.<p> + * Also note that the audio session id (from {@link #getAudioSessionId}) may + * change from its previously returned value when the VideoView is restored. */ public class VideoView extends SurfaceView implements MediaPlayerControl, SubtitleController.Anchor { diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 525517c00eb5..43c4b49ce946 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -68,6 +68,8 @@ interface IBatteryStats { void noteFullWifiLockReleasedFromSource(in WorkSource ws); void noteWifiScanStartedFromSource(in WorkSource ws); void noteWifiScanStoppedFromSource(in WorkSource ws); + void noteWifiBatchedScanStartedFromSource(in WorkSource ws, int csph); + void noteWifiBatchedScanStoppedFromSource(in WorkSource ws); void noteWifiMulticastEnabledFromSource(in WorkSource ws); void noteWifiMulticastDisabledFromSource(in WorkSource ws); void noteNetworkInterfaceType(String iface, int type); diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java new file mode 100644 index 000000000000..47d2a9c3468d --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import com.android.internal.R; + +import android.app.Dialog; +import android.content.Context; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.Comparator; + +/** + * This class implements the route chooser dialog for {@link MediaRouter}. + * <p> + * This dialog allows the user to choose a route that matches a given selector. + * </p> + * + * @see MediaRouteButton + * @see MediaRouteActionProvider + * + * TODO: Move this back into the API, as in the support library media router. + */ +public class MediaRouteChooserDialog extends Dialog { + private final MediaRouter mRouter; + private final MediaRouterCallback mCallback; + + private int mRouteTypes; + private View.OnClickListener mExtendedSettingsClickListener; + private RouteAdapter mAdapter; + private ListView mListView; + private Button mExtendedSettingsButton; + private boolean mAttachedToWindow; + + public MediaRouteChooserDialog(Context context, int theme) { + super(context, theme); + + mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + mCallback = new MediaRouterCallback(); + } + + /** + * Gets the media route types for filtering the routes that the user can + * select using the media route chooser dialog. + * + * @return The route types. + */ + public int getRouteTypes() { + return mRouteTypes; + } + + /** + * Sets the types of routes that will be shown in the media route chooser dialog + * launched by this button. + * + * @param types The route types to match. + */ + public void setRouteTypes(int types) { + if (mRouteTypes != types) { + mRouteTypes = types; + + if (mAttachedToWindow) { + mRouter.removeCallback(mCallback); + mRouter.addCallback(types, mCallback, + MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + } + + refreshRoutes(); + } + } + + public void setExtendedSettingsClickListener(View.OnClickListener listener) { + if (listener != mExtendedSettingsClickListener) { + mExtendedSettingsClickListener = listener; + updateExtendedSettingsButton(); + } + } + + /** + * Returns true if the route should be included in the list. + * <p> + * The default implementation returns true for enabled non-default routes that + * match the route types. Subclasses can override this method to filter routes + * differently. + * </p> + * + * @param route The route to consider, never null. + * @return True if the route should be included in the chooser dialog. + */ + public boolean onFilterRoute(MediaRouter.RouteInfo route) { + return !route.isDefault() && route.isEnabled() && route.matchesTypes(mRouteTypes); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().requestFeature(Window.FEATURE_LEFT_ICON); + + setContentView(R.layout.media_route_chooser_dialog); + setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY + ? R.string.media_route_chooser_title_for_remote_display + : R.string.media_route_chooser_title); + + // Must be called after setContentView. + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, + R.drawable.ic_media_route_off_holo_dark); + + mAdapter = new RouteAdapter(getContext()); + mListView = (ListView)findViewById(R.id.media_route_list); + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener(mAdapter); + mListView.setEmptyView(findViewById(android.R.id.empty)); + + mExtendedSettingsButton = (Button)findViewById(R.id.media_route_extended_settings_button); + updateExtendedSettingsButton(); + } + + private void updateExtendedSettingsButton() { + if (mExtendedSettingsButton != null) { + mExtendedSettingsButton.setOnClickListener(mExtendedSettingsClickListener); + mExtendedSettingsButton.setVisibility( + mExtendedSettingsClickListener != null ? View.VISIBLE : View.GONE); + } + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + mAttachedToWindow = true; + mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + refreshRoutes(); + } + + @Override + public void onDetachedFromWindow() { + mAttachedToWindow = false; + mRouter.removeCallback(mCallback); + + super.onDetachedFromWindow(); + } + + /** + * Refreshes the list of routes that are shown in the chooser dialog. + */ + public void refreshRoutes() { + if (mAttachedToWindow) { + mAdapter.update(); + } + } + + private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo> + implements ListView.OnItemClickListener { + private final LayoutInflater mInflater; + + public RouteAdapter(Context context) { + super(context, 0); + mInflater = LayoutInflater.from(context); + } + + public void update() { + clear(); + final int count = mRouter.getRouteCount(); + for (int i = 0; i < count; i++) { + MediaRouter.RouteInfo route = mRouter.getRouteAt(i); + if (onFilterRoute(route)) { + add(route); + } + } + sort(RouteComparator.sInstance); + notifyDataSetChanged(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + view = mInflater.inflate(R.layout.media_route_list_item, parent, false); + } + MediaRouter.RouteInfo route = getItem(position); + TextView text1 = (TextView)view.findViewById(android.R.id.text1); + TextView text2 = (TextView)view.findViewById(android.R.id.text2); + text1.setText(route.getName()); + CharSequence description = route.getDescription(); + if (TextUtils.isEmpty(description)) { + text2.setVisibility(View.GONE); + text2.setText(""); + } else { + text2.setVisibility(View.VISIBLE); + text2.setText(description); + } + view.setEnabled(route.isEnabled()); + return view; + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + MediaRouter.RouteInfo route = getItem(position); + if (route.isEnabled()) { + route.select(); + dismiss(); + } + } + } + + private final class MediaRouterCallback extends MediaRouter.SimpleCallback { + @Override + public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) { + refreshRoutes(); + } + + @Override + public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) { + refreshRoutes(); + } + + @Override + public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) { + refreshRoutes(); + } + + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + dismiss(); + } + } + + private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> { + public static final RouteComparator sInstance = new RouteComparator(); + + @Override + public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) { + return lhs.getName().toString().compareTo(rhs.getName().toString()); + } + } +} diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java index e30002185a68..ae362af74837 100644 --- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java +++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2013 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. @@ -16,675 +16,86 @@ package com.android.internal.app; -import com.android.internal.R; - -import android.app.Activity; import android.app.Dialog; import android.app.DialogFragment; -import android.app.MediaRouteActionProvider; -import android.app.MediaRouteButton; import android.content.Context; -import android.graphics.drawable.Drawable; -import android.hardware.display.DisplayManager; -import android.media.MediaRouter; -import android.media.MediaRouter.RouteCategory; -import android.media.MediaRouter.RouteGroup; -import android.media.MediaRouter.RouteInfo; import android.os.Bundle; -import android.text.TextUtils; -import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.Checkable; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.SeekBar; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import android.view.View.OnClickListener; /** - * This class implements the route chooser dialog for {@link MediaRouter}. + * Media route chooser dialog fragment. + * <p> + * Creates a {@link MediaRouteChooserDialog}. The application may subclass + * this dialog fragment to customize the media route chooser dialog. + * </p> * - * @see MediaRouteButton - * @see MediaRouteActionProvider + * TODO: Move this back into the API, as in the support library media router. */ public class MediaRouteChooserDialogFragment extends DialogFragment { - private static final String TAG = "MediaRouteChooserDialogFragment"; - public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment"; - - private static final int[] ITEM_LAYOUTS = new int[] { - R.layout.media_route_list_item_top_header, - R.layout.media_route_list_item_section_header, - R.layout.media_route_list_item, - R.layout.media_route_list_item_checkable, - R.layout.media_route_list_item_collapse_group - }; + private final String ARGUMENT_ROUTE_TYPES = "routeTypes"; - MediaRouter mRouter; - private int mRouteTypes; - - private LayoutInflater mInflater; - private LauncherListener mLauncherListener; - private View.OnClickListener mExtendedSettingsListener; - private RouteAdapter mAdapter; - private ListView mListView; - private SeekBar mVolumeSlider; - private ImageView mVolumeIcon; - - final RouteComparator mComparator = new RouteComparator(); - final MediaRouterCallback mCallback = new MediaRouterCallback(); - private boolean mIgnoreSliderVolumeChanges; - private boolean mIgnoreCallbackVolumeChanges; + private View.OnClickListener mExtendedSettingsClickListener; + /** + * Creates a media route chooser dialog fragment. + * <p> + * All subclasses of this class must also possess a default constructor. + * </p> + */ public MediaRouteChooserDialogFragment() { - setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog); + setCancelable(true); + setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog); } - public void setLauncherListener(LauncherListener listener) { - mLauncherListener = listener; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE); - mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); - } - - @Override - public void onDetach() { - super.onDetach(); - if (mLauncherListener != null) { - mLauncherListener.onDetached(this); - } - if (mAdapter != null) { - mAdapter = null; - } - mInflater = null; - mRouter.removeCallback(mCallback); - mRouter = null; - } - - public void setExtendedSettingsClickListener(View.OnClickListener listener) { - mExtendedSettingsListener = listener; + public int getRouteTypes() { + Bundle args = getArguments(); + return args != null ? args.getInt(ARGUMENT_ROUTE_TYPES) : 0; } public void setRouteTypes(int types) { - mRouteTypes = types; - } - - void updateVolume() { - if (mRouter == null) return; - - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - mVolumeIcon.setImageResource(selectedRoute == null || - selectedRoute.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_LOCAL ? - R.drawable.ic_audio_vol : R.drawable.ic_media_route_on_holo_dark); - - mIgnoreSliderVolumeChanges = true; - - if (selectedRoute == null || - selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_FIXED) { - // Disable the slider and show it at max volume. - mVolumeSlider.setMax(1); - mVolumeSlider.setProgress(1); - mVolumeSlider.setEnabled(false); - } else { - mVolumeSlider.setEnabled(true); - mVolumeSlider.setMax(selectedRoute.getVolumeMax()); - mVolumeSlider.setProgress(selectedRoute.getVolume()); - } - - mIgnoreSliderVolumeChanges = false; - } - - void changeVolume(int newValue) { - if (mIgnoreSliderVolumeChanges) return; - - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - if (selectedRoute != null && - selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_VARIABLE) { - final int maxVolume = selectedRoute.getVolumeMax(); - newValue = Math.max(0, Math.min(newValue, maxVolume)); - selectedRoute.requestSetVolume(newValue); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mInflater = inflater; - final View layout = inflater.inflate(R.layout.media_route_chooser_layout, container, false); - - mVolumeIcon = (ImageView) layout.findViewById(R.id.volume_icon); - mVolumeSlider = (SeekBar) layout.findViewById(R.id.volume_slider); - updateVolume(); - mVolumeSlider.setOnSeekBarChangeListener(new VolumeSliderChangeListener()); - - if (mExtendedSettingsListener != null) { - final View extendedSettingsButton = layout.findViewById(R.id.extended_settings); - extendedSettingsButton.setVisibility(View.VISIBLE); - extendedSettingsButton.setOnClickListener(mExtendedSettingsListener); - } - - final ListView list = (ListView) layout.findViewById(R.id.list); - list.setItemsCanFocus(true); - list.setAdapter(mAdapter = new RouteAdapter()); - list.setOnItemClickListener(mAdapter); - - mListView = list; - - mAdapter.scrollToSelectedItem(); - - return layout; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new RouteChooserDialog(getActivity(), getTheme()); - } - - private static class ViewHolder { - public TextView text1; - public TextView text2; - public ImageView icon; - public ImageButton expandGroupButton; - public RouteAdapter.ExpandGroupListener expandGroupListener; - public int position; - public CheckBox check; - } - - private class RouteAdapter extends BaseAdapter implements ListView.OnItemClickListener { - private static final int VIEW_TOP_HEADER = 0; - private static final int VIEW_SECTION_HEADER = 1; - private static final int VIEW_ROUTE = 2; - private static final int VIEW_GROUPING_ROUTE = 3; - private static final int VIEW_GROUPING_DONE = 4; - - private int mSelectedItemPosition = -1; - private final ArrayList<Object> mItems = new ArrayList<Object>(); - - private RouteCategory mCategoryEditingGroups; - private RouteGroup mEditingGroup; - - // Temporary lists for manipulation - private final ArrayList<RouteInfo> mCatRouteList = new ArrayList<RouteInfo>(); - private final ArrayList<RouteInfo> mSortRouteList = new ArrayList<RouteInfo>(); - - private boolean mIgnoreUpdates; - - RouteAdapter() { - update(); - } - - void update() { - /* - * This is kind of wacky, but our data sets are going to be - * fairly small on average. Ideally we should be able to do some of this stuff - * in-place instead. - * - * Basic idea: each entry in mItems represents an item in the list for quick access. - * Entries can be a RouteCategory (section header), a RouteInfo with a category of - * mCategoryEditingGroups (a flattened RouteInfo pulled out of its group, allowing - * the user to change the group), - */ - if (mIgnoreUpdates) return; - - mItems.clear(); - - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - mSelectedItemPosition = -1; - - List<RouteInfo> routes; - final int catCount = mRouter.getCategoryCount(); - for (int i = 0; i < catCount; i++) { - final RouteCategory cat = mRouter.getCategoryAt(i); - routes = cat.getRoutes(mCatRouteList); - - if (!cat.isSystem()) { - mItems.add(cat); - } - - if (cat == mCategoryEditingGroups) { - addGroupEditingCategoryRoutes(routes); - } else { - addSelectableRoutes(selectedRoute, routes); - } - - routes.clear(); + if (types != getRouteTypes()) { + Bundle args = getArguments(); + if (args == null) { + args = new Bundle(); } + args.putInt(ARGUMENT_ROUTE_TYPES, types); + setArguments(args); - notifyDataSetChanged(); - if (mListView != null && mSelectedItemPosition >= 0) { - mListView.setItemChecked(mSelectedItemPosition, true); - } - } - - void scrollToEditingGroup() { - if (mCategoryEditingGroups == null || mListView == null) return; - - int pos = 0; - int bound = 0; - final int itemCount = mItems.size(); - for (int i = 0; i < itemCount; i++) { - final Object item = mItems.get(i); - if (item != null && item == mCategoryEditingGroups) { - bound = i; - } - if (item == null) { - pos = i; - break; // this is always below the category header; we can stop here. - } - } - - mListView.smoothScrollToPosition(pos, bound); - } - - void scrollToSelectedItem() { - if (mListView == null || mSelectedItemPosition < 0) return; - - mListView.smoothScrollToPosition(mSelectedItemPosition); - } - - void addSelectableRoutes(RouteInfo selectedRoute, List<RouteInfo> from) { - final int routeCount = from.size(); - for (int j = 0; j < routeCount; j++) { - final RouteInfo info = from.get(j); - if (info == selectedRoute) { - mSelectedItemPosition = mItems.size(); - } - mItems.add(info); - } - } - - void addGroupEditingCategoryRoutes(List<RouteInfo> from) { - // Unpack groups and flatten for presentation - // mSortRouteList will always be empty here. - final int topCount = from.size(); - for (int i = 0; i < topCount; i++) { - final RouteInfo route = from.get(i); - final RouteGroup group = route.getGroup(); - if (group == route) { - // This is a group, unpack it. - final int groupCount = group.getRouteCount(); - for (int j = 0; j < groupCount; j++) { - final RouteInfo innerRoute = group.getRouteAt(j); - mSortRouteList.add(innerRoute); - } - } else { - mSortRouteList.add(route); - } - } - // Sort by name. This will keep the route positions relatively stable even though they - // will be repeatedly added and removed. - Collections.sort(mSortRouteList, mComparator); - - mItems.addAll(mSortRouteList); - mSortRouteList.clear(); - - mItems.add(null); // Sentinel reserving space for the "done" button. - } - - @Override - public int getCount() { - return mItems.size(); - } - - @Override - public int getViewTypeCount() { - return 5; - } - - @Override - public int getItemViewType(int position) { - final Object item = getItem(position); - if (item instanceof RouteCategory) { - return position == 0 ? VIEW_TOP_HEADER : VIEW_SECTION_HEADER; - } else if (item == null) { - return VIEW_GROUPING_DONE; - } else { - final RouteInfo info = (RouteInfo) item; - if (info.getCategory() == mCategoryEditingGroups) { - return VIEW_GROUPING_ROUTE; - } - return VIEW_ROUTE; - } - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - switch (getItemViewType(position)) { - case VIEW_ROUTE: - return ((RouteInfo) mItems.get(position)).isEnabled(); - case VIEW_GROUPING_ROUTE: - case VIEW_GROUPING_DONE: - return true; - default: - return false; - } - } - - @Override - public Object getItem(int position) { - return mItems.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final int viewType = getItemViewType(position); - - ViewHolder holder; - if (convertView == null) { - convertView = mInflater.inflate(ITEM_LAYOUTS[viewType], parent, false); - holder = new ViewHolder(); - holder.position = position; - holder.text1 = (TextView) convertView.findViewById(R.id.text1); - holder.text2 = (TextView) convertView.findViewById(R.id.text2); - holder.icon = (ImageView) convertView.findViewById(R.id.icon); - holder.check = (CheckBox) convertView.findViewById(R.id.check); - holder.expandGroupButton = (ImageButton) convertView.findViewById( - R.id.expand_button); - if (holder.expandGroupButton != null) { - holder.expandGroupListener = new ExpandGroupListener(); - holder.expandGroupButton.setOnClickListener(holder.expandGroupListener); - } - - final View fview = convertView; - final ListView list = (ListView) parent; - final ViewHolder fholder = holder; - convertView.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View v) { - list.performItemClick(fview, fholder.position, 0); - } - }); - convertView.setTag(holder); - } else { - holder = (ViewHolder) convertView.getTag(); - holder.position = position; - } - - switch (viewType) { - case VIEW_ROUTE: - case VIEW_GROUPING_ROUTE: - bindItemView(position, holder); - break; - case VIEW_SECTION_HEADER: - case VIEW_TOP_HEADER: - bindHeaderView(position, holder); - break; - } - - convertView.setActivated(position == mSelectedItemPosition); - convertView.setEnabled(isEnabled(position)); - - return convertView; - } - - void bindItemView(int position, ViewHolder holder) { - RouteInfo info = (RouteInfo) mItems.get(position); - holder.text1.setText(info.getName(getActivity())); - final CharSequence status = info.getStatus(); - if (TextUtils.isEmpty(status)) { - holder.text2.setVisibility(View.GONE); - } else { - holder.text2.setVisibility(View.VISIBLE); - holder.text2.setText(status); - } - Drawable icon = info.getIconDrawable(); - if (icon != null) { - // Make sure we have a fresh drawable where it doesn't matter if we mutate it - icon = icon.getConstantState().newDrawable(getResources()); - } - holder.icon.setImageDrawable(icon); - holder.icon.setVisibility(icon != null ? View.VISIBLE : View.GONE); - - RouteCategory cat = info.getCategory(); - boolean canGroup = false; - if (cat == mCategoryEditingGroups) { - RouteGroup group = info.getGroup(); - holder.check.setEnabled(group.getRouteCount() > 1); - holder.check.setChecked(group == mEditingGroup); - } else { - if (cat.isGroupable()) { - final RouteGroup group = (RouteGroup) info; - canGroup = group.getRouteCount() > 1 || - getItemViewType(position - 1) == VIEW_ROUTE || - (position < getCount() - 1 && - getItemViewType(position + 1) == VIEW_ROUTE); - } - } - - if (holder.expandGroupButton != null) { - holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE); - holder.expandGroupListener.position = position; - } - } - - void bindHeaderView(int position, ViewHolder holder) { - RouteCategory cat = (RouteCategory) mItems.get(position); - holder.text1.setText(cat.getName(getActivity())); - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final int type = getItemViewType(position); - if (type == VIEW_SECTION_HEADER || type == VIEW_TOP_HEADER) { - return; - } else if (type == VIEW_GROUPING_DONE) { - finishGrouping(); - return; - } else { - final Object item = getItem(position); - if (!(item instanceof RouteInfo)) { - // Oops. Stale event running around? Skip it. - return; - } - - final RouteInfo route = (RouteInfo) item; - if (type == VIEW_ROUTE) { - mRouter.selectRouteInt(mRouteTypes, route); - dismiss(); - } else if (type == VIEW_GROUPING_ROUTE) { - final Checkable c = (Checkable) view; - final boolean wasChecked = c.isChecked(); - - mIgnoreUpdates = true; - RouteGroup oldGroup = route.getGroup(); - if (!wasChecked && oldGroup != mEditingGroup) { - // Assumption: in a groupable category oldGroup will never be null. - if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) { - // Old group was selected but is now empty. Select the group - // we're manipulating since that's where the last route went. - mRouter.selectRouteInt(mRouteTypes, mEditingGroup); - } - oldGroup.removeRoute(route); - mEditingGroup.addRoute(route); - c.setChecked(true); - } else if (wasChecked && mEditingGroup.getRouteCount() > 1) { - mEditingGroup.removeRoute(route); - - // In a groupable category this will add - // the route into its own new group. - mRouter.addRouteInt(route); - } - mIgnoreUpdates = false; - update(); - } - } - } - - boolean isGrouping() { - return mCategoryEditingGroups != null; - } - - void finishGrouping() { - mCategoryEditingGroups = null; - mEditingGroup = null; - getDialog().setCanceledOnTouchOutside(true); - update(); - scrollToSelectedItem(); - } - - class ExpandGroupListener implements View.OnClickListener { - int position; - - @Override - public void onClick(View v) { - // Assumption: this is only available for the user to click if we're presenting - // a groupable category, where every top-level route in the category is a group. - final RouteGroup group = (RouteGroup) getItem(position); - mEditingGroup = group; - mCategoryEditingGroups = group.getCategory(); - getDialog().setCanceledOnTouchOutside(false); - mRouter.selectRouteInt(mRouteTypes, mEditingGroup); - update(); - scrollToEditingGroup(); - } - } - } - - class MediaRouterCallback extends MediaRouter.Callback { - @Override - public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { - mAdapter.update(); - updateVolume(); - } - - @Override - public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { - mAdapter.update(); - } - - @Override - public void onRouteAdded(MediaRouter router, RouteInfo info) { - mAdapter.update(); - } - - @Override - public void onRouteRemoved(MediaRouter router, RouteInfo info) { - if (info == mAdapter.mEditingGroup) { - mAdapter.finishGrouping(); - } - mAdapter.update(); - } - - @Override - public void onRouteChanged(MediaRouter router, RouteInfo info) { - mAdapter.notifyDataSetChanged(); - } - - @Override - public void onRouteGrouped(MediaRouter router, RouteInfo info, - RouteGroup group, int index) { - mAdapter.update(); - } - - @Override - public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { - mAdapter.update(); - } - - @Override - public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) { - if (!mIgnoreCallbackVolumeChanges) { - updateVolume(); + MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog(); + if (dialog != null) { + dialog.setRouteTypes(types); } } } - class RouteComparator implements Comparator<RouteInfo> { - @Override - public int compare(RouteInfo lhs, RouteInfo rhs) { - return lhs.getName(getActivity()).toString() - .compareTo(rhs.getName(getActivity()).toString()); - } - } - - class RouteChooserDialog extends Dialog { - public RouteChooserDialog(Context context, int theme) { - super(context, theme); - } - - @Override - public void onBackPressed() { - if (mAdapter != null && mAdapter.isGrouping()) { - mAdapter.finishGrouping(); - } else { - super.onBackPressed(); - } - } - - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) { - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - if (selectedRoute != null) { - selectedRoute.requestUpdateVolume(-1); - return true; - } - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) { - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - if (selectedRoute != null) { - mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(1); - return true; - } - } - return super.onKeyDown(keyCode, event); - } + public void setExtendedSettingsClickListener(View.OnClickListener listener) { + if (listener != mExtendedSettingsClickListener) { + mExtendedSettingsClickListener = listener; - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) { - return true; - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) { - return true; - } else { - return super.onKeyUp(keyCode, event); + MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog(); + if (dialog != null) { + dialog.setExtendedSettingsClickListener(listener); } } } /** - * Implemented by the MediaRouteButton that launched this dialog + * Called when the chooser dialog is being created. + * <p> + * Subclasses may override this method to customize the dialog. + * </p> */ - public interface LauncherListener { - public void onDetached(MediaRouteChooserDialogFragment detachedFragment); + public MediaRouteChooserDialog onCreateChooserDialog( + Context context, Bundle savedInstanceState) { + return new MediaRouteChooserDialog(context, getTheme()); } - class VolumeSliderChangeListener implements SeekBar.OnSeekBarChangeListener { - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - changeVolume(progress); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - mIgnoreCallbackVolumeChanges = true; - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - mIgnoreCallbackVolumeChanges = false; - updateVolume(); - } - + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + MediaRouteChooserDialog dialog = onCreateChooserDialog(getActivity(), savedInstanceState); + dialog.setRouteTypes(getRouteTypes()); + dialog.setExtendedSettingsClickListener(mExtendedSettingsClickListener); + return dialog; } } diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java new file mode 100644 index 000000000000..8fc99c730501 --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import com.android.internal.R; + +import android.app.Dialog; +import android.app.MediaRouteActionProvider; +import android.app.MediaRouteButton; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteGroup; +import android.media.MediaRouter.RouteInfo; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.SeekBar; + +/** + * This class implements the route controller dialog for {@link MediaRouter}. + * <p> + * This dialog allows the user to control or disconnect from the currently selected route. + * </p> + * + * @see MediaRouteButton + * @see MediaRouteActionProvider + * + * TODO: Move this back into the API, as in the support library media router. + */ +public class MediaRouteControllerDialog extends Dialog { + // Time to wait before updating the volume when the user lets go of the seek bar + // to allow the route provider time to propagate the change and publish a new + // route descriptor. + private static final int VOLUME_UPDATE_DELAY_MILLIS = 250; + + private final MediaRouter mRouter; + private final MediaRouterCallback mCallback; + private final MediaRouter.RouteInfo mRoute; + + private boolean mCreated; + private Drawable mMediaRouteConnectingDrawable; + private Drawable mMediaRouteOnDrawable; + private Drawable mCurrentIconDrawable; + + private boolean mVolumeControlEnabled = true; + private LinearLayout mVolumeLayout; + private SeekBar mVolumeSlider; + private boolean mVolumeSliderTouched; + + private View mControlView; + + private Button mDisconnectButton; + + public MediaRouteControllerDialog(Context context, int theme) { + super(context, theme); + + mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + mCallback = new MediaRouterCallback(); + mRoute = mRouter.getSelectedRoute(); + } + + /** + * Gets the route that this dialog is controlling. + */ + public MediaRouter.RouteInfo getRoute() { + return mRoute; + } + + /** + * Provides the subclass an opportunity to create a view that will + * be included within the body of the dialog to offer additional media controls + * for the currently playing content. + * + * @param savedInstanceState The dialog's saved instance state. + * @return The media control view, or null if none. + */ + public View onCreateMediaControlView(Bundle savedInstanceState) { + return null; + } + + /** + * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}. + * + * @return The media control view, or null if none. + */ + public View getMediaControlView() { + return mControlView; + } + + /** + * Sets whether to enable the volume slider and volume control using the volume keys + * when the route supports it. + * <p> + * The default value is true. + * </p> + */ + public void setVolumeControlEnabled(boolean enable) { + if (mVolumeControlEnabled != enable) { + mVolumeControlEnabled = enable; + if (mCreated) { + updateVolume(); + } + } + } + + /** + * Returns whether to enable the volume slider and volume control using the volume keys + * when the route supports it. + */ + public boolean isVolumeControlEnabled() { + return mVolumeControlEnabled; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().requestFeature(Window.FEATURE_LEFT_ICON); + + setContentView(R.layout.media_route_controller_dialog); + + mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout); + mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider); + mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + private final Runnable mStopTrackingTouch = new Runnable() { + @Override + public void run() { + if (mVolumeSliderTouched) { + mVolumeSliderTouched = false; + updateVolume(); + } + } + }; + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (mVolumeSliderTouched) { + mVolumeSlider.removeCallbacks(mStopTrackingTouch); + } else { + mVolumeSliderTouched = true; + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Defer resetting mVolumeSliderTouched to allow the media route provider + // a little time to settle into its new state and publish the final + // volume update. + mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + mRoute.requestSetVolume(progress); + } + } + }); + + mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button); + mDisconnectButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mRoute.isSelected()) { + mRouter.getDefaultRoute().select(); + } + dismiss(); + } + }); + + mCreated = true; + if (update()) { + mControlView = onCreateMediaControlView(savedInstanceState); + FrameLayout controlFrame = + (FrameLayout)findViewById(R.id.media_route_control_frame); + if (mControlView != null) { + controlFrame.addView(mControlView); + controlFrame.setVisibility(View.VISIBLE); + } else { + controlFrame.setVisibility(View.GONE); + } + } + } + + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS); + update(); + } + + @Override + public void onDetachedFromWindow() { + mRouter.removeCallback(mCallback); + + super.onDetachedFromWindow(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + return true; + } + return super.onKeyUp(keyCode, event); + } + + private boolean update() { + if (!mRoute.isSelected() || mRoute.isDefault()) { + dismiss(); + return false; + } + + setTitle(mRoute.getName()); + updateVolume(); + + Drawable icon = getIconDrawable(); + if (icon != mCurrentIconDrawable) { + mCurrentIconDrawable = icon; + getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon); + } + return true; + } + + private Drawable getIconDrawable() { + if (mRoute.isConnecting()) { + if (mMediaRouteConnectingDrawable == null) { + mMediaRouteConnectingDrawable = getContext().getResources().getDrawable( + R.drawable.ic_media_route_connecting_holo_dark); + } + return mMediaRouteConnectingDrawable; + } else { + if (mMediaRouteOnDrawable == null) { + mMediaRouteOnDrawable = getContext().getResources().getDrawable( + R.drawable.ic_media_route_on_holo_dark); + } + return mMediaRouteOnDrawable; + } + } + + private void updateVolume() { + if (!mVolumeSliderTouched) { + if (isVolumeControlAvailable()) { + mVolumeLayout.setVisibility(View.VISIBLE); + mVolumeSlider.setMax(mRoute.getVolumeMax()); + mVolumeSlider.setProgress(mRoute.getVolume()); + } else { + mVolumeLayout.setVisibility(View.GONE); + } + } + } + + private boolean isVolumeControlAvailable() { + return mVolumeControlEnabled && mRoute.getVolumeHandling() == + MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; + } + + private final class MediaRouterCallback extends MediaRouter.SimpleCallback { + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + update(); + } + + @Override + public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { + update(); + } + + @Override + public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { + if (route == mRoute) { + updateVolume(); + } + } + + @Override + public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, + int index) { + update(); + } + + @Override + public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { + update(); + } + } +} diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java new file mode 100644 index 000000000000..108e81fd5e02 --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.os.Bundle; + +/** + * Media route controller dialog fragment. + * <p> + * Creates a {@link MediaRouteControllerDialog}. The application may subclass + * this dialog fragment to customize the media route controller dialog. + * </p> + * + * TODO: Move this back into the API, as in the support library media router. + */ +public class MediaRouteControllerDialogFragment extends DialogFragment { + /** + * Creates a media route controller dialog fragment. + * <p> + * All subclasses of this class must also possess a default constructor. + * </p> + */ + public MediaRouteControllerDialogFragment() { + setCancelable(true); + setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog); + } + + /** + * Called when the controller dialog is being created. + * <p> + * Subclasses may override this method to customize the dialog. + * </p> + */ + public MediaRouteControllerDialog onCreateControllerDialog( + Context context, Bundle savedInstanceState) { + return new MediaRouteControllerDialog(context, getTheme()); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return onCreateControllerDialog(getActivity(), savedInstanceState); + } +} diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java new file mode 100644 index 000000000000..fad7fd493dc4 --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + + +import android.app.Activity; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.Context; +import android.media.MediaRouter; +import android.util.Log; +import android.view.View; + +/** + * Shows media route dialog as appropriate. + * @hide + */ +public abstract class MediaRouteDialogPresenter { + private static final String TAG = "MediaRouter"; + + private static final String CHOOSER_FRAGMENT_TAG = + "android.app.MediaRouteButton:MediaRouteChooserDialogFragment"; + private static final String CONTROLLER_FRAGMENT_TAG = + "android.app.MediaRouteButton:MediaRouteControllerDialogFragment"; + + public static DialogFragment showDialogFragment(Activity activity, + int routeTypes, View.OnClickListener extendedSettingsClickListener) { + final MediaRouter router = (MediaRouter)activity.getSystemService( + Context.MEDIA_ROUTER_SERVICE); + final FragmentManager fm = activity.getFragmentManager(); + + MediaRouter.RouteInfo route = router.getSelectedRoute(); + if (route.isDefault() || !route.matchesTypes(routeTypes)) { + if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) { + Log.w(TAG, "showDialog(): Route chooser dialog already showing!"); + return null; + } + MediaRouteChooserDialogFragment f = new MediaRouteChooserDialogFragment(); + f.setRouteTypes(routeTypes); + f.setExtendedSettingsClickListener(extendedSettingsClickListener); + f.show(fm, CHOOSER_FRAGMENT_TAG); + return f; + } else { + if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) { + Log.w(TAG, "showDialog(): Route controller dialog already showing!"); + return null; + } + MediaRouteControllerDialogFragment f = new MediaRouteControllerDialogFragment(); + f.show(fm, CONTROLLER_FRAGMENT_TAG); + return f; + } + } + + public static Dialog createDialog(Context context, + int routeTypes, View.OnClickListener extendedSettingsClickListener) { + final MediaRouter router = (MediaRouter)context.getSystemService( + Context.MEDIA_ROUTER_SERVICE); + + MediaRouter.RouteInfo route = router.getSelectedRoute(); + if (route.isDefault() || !route.matchesTypes(routeTypes)) { + final MediaRouteChooserDialog d = new MediaRouteChooserDialog( + context, android.R.style.Theme_DeviceDefault_Dialog); + d.setRouteTypes(routeTypes); + d.setExtendedSettingsClickListener(extendedSettingsClickListener); + return d; + } else { + MediaRouteControllerDialog d = new MediaRouteControllerDialog( + context, android.R.style.Theme_DeviceDefault_Dialog); + return d; + } + } +} diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java index 20b8c95164cd..0aad0b541252 100644 --- a/core/java/com/android/internal/app/ProcessStats.java +++ b/core/java/com/android/internal/app/ProcessStats.java @@ -1004,7 +1004,7 @@ public final class ProcessStats implements Parcelable { for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) { ProcessState ps = pkgState.mProcesses.valueAt(iproc); if (ps.isInUse() || ps.mCommonProcess.isInUse()) { - pkgState.mProcesses.valueAt(iproc).resetSafely(now); + ps.resetSafely(now); } else { pkgState.mProcesses.valueAt(iproc).makeDead(); pkgState.mProcesses.removeAt(iproc); @@ -1013,7 +1013,7 @@ public final class ProcessStats implements Parcelable { for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) { ServiceState ss = pkgState.mServices.valueAt(isvc); if (ss.isInUse()) { - pkgState.mServices.valueAt(isvc).resetSafely(now); + ss.resetSafely(now); } else { pkgState.mServices.removeAt(isvc); } @@ -1758,21 +1758,34 @@ public final class ProcessStats implements Parcelable { mStartTime, now); ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); boolean printedHeader = false; + boolean sepNeeded = false; for (int ip=0; ip<pkgMap.size(); ip++) { - String pkgName = pkgMap.keyAt(ip); - if (reqPackage != null && !reqPackage.equals(pkgName)) { - continue; - } - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final String pkgName = pkgMap.keyAt(ip); + final SparseArray<PackageState> uids = pkgMap.valueAt(ip); for (int iu=0; iu<uids.size(); iu++) { - int uid = uids.keyAt(iu); - PackageState pkgState = uids.valueAt(iu); + final int uid = uids.keyAt(iu); + final PackageState pkgState = uids.valueAt(iu); final int NPROCS = pkgState.mProcesses.size(); final int NSRVS = pkgState.mServices.size(); + final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); + if (!pkgMatch) { + boolean procMatch = false; + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (reqPackage.equals(proc.mName)) { + procMatch = true; + break; + } + } + if (!procMatch) { + continue; + } + } if (NPROCS > 0 || NSRVS > 0) { if (!printedHeader) { pw.println("Per-Package Stats:"); printedHeader = true; + sepNeeded = true; } pw.print(" * "); pw.print(pkgName); pw.print(" / "); UserHandle.formatUid(pw, uid); pw.println(":"); @@ -1780,6 +1793,9 @@ public final class ProcessStats implements Parcelable { if (!dumpSummary || dumpAll) { for (int iproc=0; iproc<NPROCS; iproc++) { ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } if (activeOnly && !proc.isInUse()) { pw.print(" (Not active: "); pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")"); @@ -1787,7 +1803,11 @@ public final class ProcessStats implements Parcelable { } pw.print(" Process "); pw.print(pkgState.mProcesses.keyAt(iproc)); - pw.print(" ("); + if (proc.mCommonProcess.mMultiPackage) { + pw.print(" (multi, "); + } else { + pw.print(" (unique, "); + } pw.print(proc.mDurationsTableSize); pw.print(" entries)"); pw.println(":"); @@ -1801,6 +1821,9 @@ public final class ProcessStats implements Parcelable { ArrayList<ProcessState> procs = new ArrayList<ProcessState>(); for (int iproc=0; iproc<NPROCS; iproc++) { ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } if (activeOnly && !proc.isInUse()) { continue; } @@ -1811,6 +1834,9 @@ public final class ProcessStats implements Parcelable { } for (int isvc=0; isvc<NSRVS; isvc++) { ServiceState svc = pkgState.mServices.valueAt(isvc); + if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) { + continue; + } if (activeOnly && !svc.isInUse()) { pw.print(" (Not active: "); pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")"); @@ -1840,64 +1866,73 @@ public final class ProcessStats implements Parcelable { if (svc.mOwner != null) { pw.print(" mOwner="); pw.println(svc.mOwner); } + if (svc.mStarted || svc.mRestarting) { + pw.print(" mStarted="); pw.print(svc.mStarted); + pw.print(" mRestarting="); pw.println(svc.mRestarting); + } } } } } - if (reqPackage == null) { - ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); - printedHeader = false; - int numShownProcs = 0, numTotalProcs = 0; - for (int ip=0; ip<procMap.size(); ip++) { - String procName = procMap.keyAt(ip); - SparseArray<ProcessState> uids = procMap.valueAt(ip); - for (int iu=0; iu<uids.size(); iu++) { - int uid = uids.keyAt(iu); - numTotalProcs++; - ProcessState proc = uids.valueAt(iu); - if (proc.mDurationsTableSize == 0 && proc.mCurState == STATE_NOTHING - && proc.mPssTableSize == 0) { - continue; - } - numShownProcs++; - if (!printedHeader) { - pw.println(); - pw.println("Per-Process Stats:"); - printedHeader = true; - } - if (activeOnly && !proc.isInUse()) { - pw.print(" (Not active: "); pw.print(procName); pw.println(")"); - continue; - } - pw.print(" * "); pw.print(procName); pw.print(" / "); - UserHandle.formatUid(pw, uid); - pw.print(" ("); pw.print(proc.mDurationsTableSize); - pw.print(" entries)"); pw.println(":"); - dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - ALL_PROC_STATES, now); - dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - ALL_PROC_STATES); - if (dumpAll) { - dumpProcessInternalLocked(pw, " ", proc, dumpAll); - } + ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); + printedHeader = false; + int numShownProcs = 0, numTotalProcs = 0; + for (int ip=0; ip<procMap.size(); ip++) { + String procName = procMap.keyAt(ip); + SparseArray<ProcessState> uids = procMap.valueAt(ip); + for (int iu=0; iu<uids.size(); iu++) { + int uid = uids.keyAt(iu); + numTotalProcs++; + ProcessState proc = uids.valueAt(iu); + if (proc.mDurationsTableSize == 0 && proc.mCurState == STATE_NOTHING + && proc.mPssTableSize == 0) { + continue; } + if (!proc.mMultiPackage) { + continue; + } + if (reqPackage != null && !reqPackage.equals(procName) + && !reqPackage.equals(proc.mPackage)) { + continue; + } + numShownProcs++; + if (sepNeeded) { + pw.println(); + } + sepNeeded = true; + if (!printedHeader) { + pw.println("Multi-Package Common Processes:"); + printedHeader = true; + } + if (activeOnly && !proc.isInUse()) { + pw.print(" (Not active: "); pw.print(procName); pw.println(")"); + continue; + } + pw.print(" * "); pw.print(procName); pw.print(" / "); + UserHandle.formatUid(pw, uid); + pw.print(" ("); pw.print(proc.mDurationsTableSize); + pw.print(" entries)"); pw.println(":"); + dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + ALL_PROC_STATES, now); + dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + ALL_PROC_STATES); + dumpProcessInternalLocked(pw, " ", proc, dumpAll); } - if (dumpAll) { - pw.println(); - pw.print(" Total procs: "); pw.print(numShownProcs); - pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total"); - } + } + if (dumpAll) { + pw.println(); + pw.print(" Total procs: "); pw.print(numShownProcs); + pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total"); + } + if (sepNeeded) { pw.println(); - if (dumpSummary) { - pw.println("Summary:"); - dumpSummaryLocked(pw, reqPackage, now, activeOnly); - } else { - dumpTotalsLocked(pw, now); - } + } + if (dumpSummary) { + pw.println("Summary:"); + dumpSummaryLocked(pw, reqPackage, now, activeOnly); } else { - pw.println(); dumpTotalsLocked(pw, now); } @@ -2031,17 +2066,20 @@ public final class ProcessStats implements Parcelable { public ArrayList<ProcessState> collectProcessesLocked(int[] screenStates, int[] memStates, int[] procStates, int sortProcStates[], long now, String reqPackage, boolean activeOnly) { - ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>(); - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); + final ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>(); + final ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); for (int ip=0; ip<pkgMap.size(); ip++) { - if (reqPackage != null && !reqPackage.equals(pkgMap.keyAt(ip))) { - continue; - } - SparseArray<PackageState> procs = pkgMap.valueAt(ip); + final String pkgName = pkgMap.keyAt(ip); + final SparseArray<PackageState> procs = pkgMap.valueAt(ip); for (int iu=0; iu<procs.size(); iu++) { - PackageState state = procs.valueAt(iu); - for (int iproc=0; iproc<state.mProcesses.size(); iproc++) { - ProcessState proc = state.mProcesses.valueAt(iproc); + final PackageState state = procs.valueAt(iu); + final int NPROCS = state.mProcesses.size(); + final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); + for (int iproc=0; iproc<NPROCS; iproc++) { + final ProcessState proc = state.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } if (activeOnly && !proc.isInUse()) { continue; } @@ -2601,23 +2639,35 @@ public final class ProcessStats implements Parcelable { } } - void incStartedServices(int memFactor, long now) { + void incStartedServices(int memFactor, long now, String serviceName) { + if (false) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.d(TAG, "incStartedServices: " + this + " service=" + serviceName + + " to " + (mNumStartedServices+1), here); + } if (mCommonProcess != this) { - mCommonProcess.incStartedServices(memFactor, now); + mCommonProcess.incStartedServices(memFactor, now, serviceName); } mNumStartedServices++; if (mNumStartedServices == 1 && mCurState == STATE_NOTHING) { - setState(STATE_NOTHING, memFactor, now, null); + setState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now); } } - void decStartedServices(int memFactor, long now) { + void decStartedServices(int memFactor, long now, String serviceName) { + if (false) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName + + " to " + (mNumStartedServices-1), here); + } if (mCommonProcess != this) { - mCommonProcess.decStartedServices(memFactor, now); + mCommonProcess.decStartedServices(memFactor, now, serviceName); } mNumStartedServices--; - if (mNumStartedServices == 0 && mCurState == STATE_SERVICE_RESTARTING) { - setState(STATE_NOTHING, memFactor, now, null); + if (mNumStartedServices == 0 && (mCurState%STATE_COUNT) == STATE_SERVICE_RESTARTING) { + setState(STATE_NOTHING, now); } else if (mNumStartedServices < 0) { Slog.wtfStack(TAG, "Proc started services underrun: pkg=" + mPackage + " uid=" + mUid + " name=" + mName); @@ -2873,6 +2923,8 @@ public final class ProcessStats implements Parcelable { public int mRunState = STATE_NOTHING; long mRunStartTime; + boolean mStarted; + boolean mRestarting; int mStartedCount; public int mStartedState = STATE_NOTHING; long mStartedStartTime; @@ -2902,10 +2954,9 @@ public final class ProcessStats implements Parcelable { // There was already an old owner, reset this object for its // new owner. mOwner = newOwner; - if (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING - || mExecState != STATE_NOTHING) { + if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING) { long now = SystemClock.uptimeMillis(); - if (mStartedState != STATE_NOTHING) { + if (mStarted) { if (DEBUG) Slog.d(TAG, "Service has new owner " + newOwner + " from " + mOwner + " while started: pkg=" + mPackage + " service=" + mName + " proc=" + mProc); @@ -2931,10 +2982,9 @@ public final class ProcessStats implements Parcelable { public void clearCurrentOwner(Object owner, boolean silently) { if (mOwner == owner) { mProc.decActiveServices(mName); - if (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING - || mExecState != STATE_NOTHING) { + if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING) { long now = SystemClock.uptimeMillis(); - if (mStartedState != STATE_NOTHING) { + if (mStarted) { if (!silently) { Slog.wtfStack(TAG, "Service owner " + owner + " cleared while started: pkg=" + mPackage + " service=" @@ -2964,7 +3014,7 @@ public final class ProcessStats implements Parcelable { } public boolean isInUse() { - return mOwner != null; + return mOwner != null || mRestarting; } void add(ServiceState other) { @@ -3042,7 +3092,18 @@ public final class ProcessStats implements Parcelable { if (mOwner == null) { Slog.wtf(TAG, "Starting service " + this + " without owner"); } + mStarted = started; + updateStartedState(memFactor, now); + } + + public void setRestarting(boolean restarting, int memFactor, long now) { + mRestarting = restarting; + updateStartedState(memFactor, now); + } + + void updateStartedState(int memFactor, long now) { final boolean wasStarted = mStartedState != STATE_NOTHING; + final boolean started = mStarted || mRestarting; final int state = started ? memFactor : STATE_NOTHING; if (mStartedState != state) { if (mStartedState != STATE_NOTHING) { @@ -3056,9 +3117,9 @@ public final class ProcessStats implements Parcelable { mProc = mProc.pullFixedProc(mPackage); if (wasStarted != started) { if (started) { - mProc.incStartedServices(memFactor, now); + mProc.incStartedServices(memFactor, now, mName); } else { - mProc.decStartedServices(memFactor, now); + mProc.decStartedServices(memFactor, now, mName); } } updateRunning(memFactor, now); diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 5bfa1b236b3d..1e37fd9935a0 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -23,6 +23,12 @@ import android.os.ParcelFileDescriptor; /** {@hide} */ interface IBackupTransport { + /** + * Ask the transport for the name under which it should be registered. This will + * typically be its host service's component name, but need not be. + */ + String name(); + /** * Ask the transport for an Intent that can be used to launch any internal * configuration Activity that it wishes to present. For example, the transport diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index eb2d1fe3e79a..494bc7898e93 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -19,6 +19,7 @@ package com.android.internal.backup; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.RestoreSet; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; @@ -71,6 +72,10 @@ public class LocalTransport extends IBackupTransport.Stub { } } + public String name() { + return new ComponentName(mContext, this.getClass()).flattenToShortString(); + } + public Intent configurationIntent() { // The local transport is not user-configurable return null; diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java new file mode 100644 index 000000000000..d05699a73ae6 --- /dev/null +++ b/core/java/com/android/internal/backup/LocalTransportService.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.backup; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class LocalTransportService extends Service { + private static LocalTransport sTransport = null; + + @Override + public void onCreate() { + if (sTransport == null) { + sTransport = new LocalTransport(this); + } + } + + @Override + public IBinder onBind(Intent intent) { + return sTransport; + } +} diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index ab871fb80a59..942995b51a83 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -372,23 +372,25 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); mAppearingPackages = pkgList; - mChangeType = PACKAGE_TEMPORARY_CHANGE; + mChangeType = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) + ? PACKAGE_UPDATING : PACKAGE_TEMPORARY_CHANGE; mSomePackagesChanged = true; if (pkgList != null) { onPackagesAvailable(pkgList); for (int i=0; i<pkgList.length; i++) { - onPackageAppeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE); + onPackageAppeared(pkgList[i], mChangeType); } } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); mDisappearingPackages = pkgList; - mChangeType = PACKAGE_TEMPORARY_CHANGE; + mChangeType = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) + ? PACKAGE_UPDATING : PACKAGE_TEMPORARY_CHANGE; mSomePackagesChanged = true; if (pkgList != null) { onPackagesUnavailable(pkgList); for (int i=0; i<pkgList.length; i++) { - onPackageDisappeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE); + onPackageDisappeared(pkgList[i], mChangeType); } } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index e0a154cea29a..0a702ff8ddec 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -84,7 +84,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 66 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 67 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -154,6 +154,8 @@ public final class BatteryStatsImpl extends BatteryStats { final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<StopwatchTimer>(); final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<StopwatchTimer>(); final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<StopwatchTimer>(); + final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = + new SparseArray<ArrayList<StopwatchTimer>>(); // Last partial timers we use for distributing CPU usage. final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>(); @@ -2172,6 +2174,9 @@ public final class BatteryStatsImpl extends BatteryStats { case TelephonyManager.NETWORK_TYPE_EHRPD: bin = DATA_CONNECTION_EHRPD; break; + case TelephonyManager.NETWORK_TYPE_HSPAP: + bin = DATA_CONNECTION_HSPAP; + break; default: bin = DATA_CONNECTION_OTHER; break; @@ -2401,6 +2406,14 @@ public final class BatteryStatsImpl extends BatteryStats { getUidStatsLocked(uid).noteWifiScanStoppedLocked(); } + public void noteWifiBatchedScanStartedLocked(int uid, int csph) { + getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph); + } + + public void noteWifiBatchedScanStoppedLocked(int uid) { + getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked(); + } + int mWifiMulticastNesting = 0; public void noteWifiMulticastEnabledLocked(int uid) { @@ -2453,6 +2466,20 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public void noteWifiBatchedScanStartedFromSourceLocked(WorkSource ws, int csph) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteWifiBatchedScanStartedLocked(ws.get(i), csph); + } + } + + public void noteWifiBatchedScanStoppedFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteWifiBatchedScanStoppedLocked(ws.get(i)); + } + } + public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) { int N = ws.size(); for (int i=0; i<N; i++) { @@ -2576,6 +2603,10 @@ public final class BatteryStatsImpl extends BatteryStats { boolean mWifiScanStarted; StopwatchTimer mWifiScanTimer; + private static final int NO_BATCHED_SCAN_STARTED = -1; + int mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED; + StopwatchTimer[] mWifiBatchedScanTimer; + boolean mWifiMulticastEnabled; StopwatchTimer mWifiMulticastTimer; @@ -2626,6 +2657,7 @@ public final class BatteryStatsImpl extends BatteryStats { mFullWifiLockTimers, mUnpluggables); mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN, mWifiScanTimers, mUnpluggables); + mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS]; mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, mWifiMulticastTimers, mUnpluggables); } @@ -2716,6 +2748,36 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public void noteWifiBatchedScanStartedLocked(int csph) { + int bin = 0; + while (csph > 8 && bin < NUM_WIFI_BATCHED_SCAN_BINS) { + csph = csph >> 3; + bin++; + } + + if (mWifiBatchedScanBinStarted == bin) return; + + if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) { + mWifiBatchedScanTimer[mWifiBatchedScanBinStarted]. + stopRunningLocked(BatteryStatsImpl.this); + } + mWifiBatchedScanBinStarted = bin; + if (mWifiBatchedScanTimer[bin] == null) { + makeWifiBatchedScanBin(bin, null); + } + mWifiBatchedScanTimer[bin].startRunningLocked(BatteryStatsImpl.this); + } + + @Override + public void noteWifiBatchedScanStoppedLocked() { + if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) { + mWifiBatchedScanTimer[mWifiBatchedScanBinStarted]. + stopRunningLocked(BatteryStatsImpl.this); + mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED; + } + } + + @Override public void noteWifiMulticastEnabledLocked() { if (!mWifiMulticastEnabled) { mWifiMulticastEnabled = true; @@ -2851,6 +2913,15 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which) { + if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0; + if (mWifiBatchedScanTimer[csphBin] == null) { + return 0; + } + return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(batteryRealtime, which); + } + + @Override public long getWifiMulticastTime(long batteryRealtime, int which) { if (mWifiMulticastTimer == null) { return 0; @@ -2911,6 +2982,24 @@ public final class BatteryStatsImpl extends BatteryStats { return mUserActivityCounters[type].getCountLocked(which); } + void makeWifiBatchedScanBin(int i, Parcel in) { + if (i < 0 || i >= NUM_WIFI_BATCHED_SCAN_BINS) return; + + ArrayList<StopwatchTimer> collected = mWifiBatchedScanTimers.get(i); + if (collected == null) { + collected = new ArrayList<StopwatchTimer>(); + mWifiBatchedScanTimers.put(i, collected); + } + if (in == null) { + mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected, + mUnpluggables); + } else { + mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected, + mUnpluggables, in); + } + } + + void initUserActivityLocked() { mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES]; for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { @@ -2971,6 +3060,14 @@ public final class BatteryStatsImpl extends BatteryStats { active |= !mWifiScanTimer.reset(BatteryStatsImpl.this, false); active |= mWifiScanStarted; } + if (mWifiBatchedScanTimer != null) { + for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (mWifiBatchedScanTimer[i] != null) { + active |= !mWifiBatchedScanTimer[i].reset(BatteryStatsImpl.this, false); + } + } + active |= (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED); + } if (mWifiMulticastTimer != null) { active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false); active |= mWifiMulticastEnabled; @@ -3077,6 +3174,11 @@ public final class BatteryStatsImpl extends BatteryStats { if (mWifiScanTimer != null) { mWifiScanTimer.detach(); } + for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (mWifiBatchedScanTimer[i] != null) { + mWifiBatchedScanTimer[i].detach(); + } + } if (mWifiMulticastTimer != null) { mWifiMulticastTimer.detach(); } @@ -3154,6 +3256,14 @@ public final class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } + for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (mWifiBatchedScanTimer[i] != null) { + out.writeInt(1); + mWifiBatchedScanTimer[i].writeToParcel(out, batteryRealtime); + } else { + out.writeInt(0); + } + } if (mWifiMulticastTimer != null) { out.writeInt(1); mWifiMulticastTimer.writeToParcel(out, batteryRealtime); @@ -3263,6 +3373,14 @@ public final class BatteryStatsImpl extends BatteryStats { } else { mWifiScanTimer = null; } + mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED; + for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (in.readInt() != 0) { + makeWifiBatchedScanBin(i, in); + } else { + mWifiBatchedScanTimer[i] = null; + } + } mWifiMulticastEnabled = false; if (in.readInt() != 0) { mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, @@ -5460,6 +5578,13 @@ public final class BatteryStatsImpl extends BatteryStats { if (in.readInt() != 0) { u.mWifiScanTimer.readSummaryFromParcelLocked(in); } + u.mWifiBatchedScanBinStarted = Uid.NO_BATCHED_SCAN_STARTED; + for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (in.readInt() != 0) { + u.makeWifiBatchedScanBin(i, null); + u.mWifiBatchedScanTimer[i].readSummaryFromParcelLocked(in); + } + } u.mWifiMulticastEnabled = false; if (in.readInt() != 0) { u.mWifiMulticastTimer.readSummaryFromParcelLocked(in); @@ -5671,6 +5796,14 @@ public final class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } + for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (u.mWifiBatchedScanTimer[i] != null) { + out.writeInt(1); + u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + } else { + out.writeInt(0); + } + } if (u.mWifiMulticastTimer != null) { out.writeInt(1); u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL); @@ -5906,6 +6039,7 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiRunningTimers.clear(); mFullWifiLockTimers.clear(); mWifiScanTimers.clear(); + mWifiBatchedScanTimers.clear(); mWifiMulticastTimers.clear(); sNumSpeedSteps = in.readInt(); diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 99a68436ceff..94750d376608 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -136,6 +136,13 @@ public class PowerProfile { public static final String POWER_CPU_SPEEDS = "cpu.speeds"; /** + * Power consumed by wif batched scaning. Broken down into bins by + * Channels Scanned per Hour. May do 1-720 scans per hour of 1-100 channels + * for a range of 1-72,000. Going logrithmic (1-8, 9-64, 65-512, 513-4096, 4097-)! + */ + public static final String POWER_WIFI_BATCHED_SCAN = "wifi.batchedscan"; + + /** * Battery capacity in milliAmpHour (mAh). */ public static final String POWER_BATTERY_CAPACITY = "battery.capacity"; @@ -171,7 +178,7 @@ public class PowerProfile { String element = parser.getName(); if (element == null) break; - + if (parsingArray && !element.equals(TAG_ARRAYITEM)) { // Finish array sPowerMap.put(arrayName, array.toArray(new Double[array.size()])); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 73d34c315c24..1548f1b041a2 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -192,10 +192,16 @@ public class ZygoteInit { static void closeServerSocket() { try { if (sServerSocket != null) { + FileDescriptor fd = sServerSocket.getFileDescriptor(); sServerSocket.close(); + if (fd != null) { + Libcore.os.close(fd); + } } } catch (IOException ex) { Log.e(TAG, "Zygote: error closing sockets", ex); + } catch (libcore.io.ErrnoException ex) { + Log.e(TAG, "Zygote: error closing descriptor", ex); } sServerSocket = null; @@ -301,6 +307,8 @@ public class ZygoteInit { count++; } catch (ClassNotFoundException e) { Log.w(TAG, "Class not found for preloading: " + line); + } catch (UnsatisfiedLinkError e) { + Log.w(TAG, "Problem preloading " + line + ": " + e); } catch (Throwable t) { Log.e(TAG, "Error preloading " + line + ".", t); if (t instanceof Error) { diff --git a/core/java/com/android/internal/view/CheckableLinearLayout.java b/core/java/com/android/internal/view/CheckableLinearLayout.java deleted file mode 100644 index 3fb7cec0e689..000000000000 --- a/core/java/com/android/internal/view/CheckableLinearLayout.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.view; - -import com.android.internal.R; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.Checkable; -import android.widget.CheckBox; -import android.widget.LinearLayout; - -public class CheckableLinearLayout extends LinearLayout implements Checkable { - private CheckBox mCheckBox; - - public CheckableLinearLayout(Context context) { - super(context); - // TODO Auto-generated constructor stub - } - - public CheckableLinearLayout(Context context, AttributeSet attrs) { - super(context, attrs); - // TODO Auto-generated constructor stub - } - - public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - // TODO Auto-generated constructor stub - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mCheckBox = (CheckBox) findViewById(R.id.check); - } - - @Override - public void setChecked(boolean checked) { - mCheckBox.setChecked(checked); - } - - @Override - public boolean isChecked() { - return mCheckBox.isChecked(); - } - - @Override - public void toggle() { - mCheckBox.toggle(); - } -} diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 12ced68b65de..3724a0816e47 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -73,5 +73,5 @@ interface IInputMethodManager { boolean switchToNextInputMethod(in IBinder token, boolean onlyCurrentIme); boolean shouldOfferSwitchingToNextInputMethod(in IBinder token); boolean setInputMethodEnabled(String id, boolean enabled); - oneway void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); + void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); } diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index 44e7ec1ac005..fe1cf72cb884 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -164,8 +164,24 @@ public class ActionMenuPresenter extends BaseMenuPresenter } actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE); + final ActionMenuView menuParent = (ActionMenuView) parent; + final ViewGroup.LayoutParams lp = actionView.getLayoutParams(); + if (!menuParent.checkLayoutParams(lp)) { + actionView.setLayoutParams(menuParent.generateLayoutParams(lp)); + } + return actionView; + } + + @Override + public void bindItemView(final MenuItemImpl item, MenuView.ItemView itemView) { + itemView.initialize(item, 0); + + final ActionMenuView menuView = (ActionMenuView) mMenuView; + final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView; + actionItemView.setItemInvoker(menuView); + if (item.hasSubMenu()) { - actionView.setOnTouchListener(new ForwardingListener(actionView) { + actionItemView.setOnTouchListener(new ForwardingListener(actionItemView) { @Override public ListPopupWindow getPopup() { return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null; @@ -182,24 +198,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter } }); } else { - actionView.setOnTouchListener(null); - } - - final ActionMenuView menuParent = (ActionMenuView) parent; - final ViewGroup.LayoutParams lp = actionView.getLayoutParams(); - if (!menuParent.checkLayoutParams(lp)) { - actionView.setLayoutParams(menuParent.generateLayoutParams(lp)); + actionItemView.setOnTouchListener(null); } - return actionView; - } - - @Override - public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) { - itemView.initialize(item, 0); - - final ActionMenuView menuView = (ActionMenuView) mMenuView; - ActionMenuItemView actionItemView = (ActionMenuItemView) itemView; - actionItemView.setItemInvoker(menuView); } @Override @@ -721,7 +721,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter if (subMenu == null) return false; mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId(); - return false; + final MenuPresenter.Callback cb = getCallback(); + return cb != null ? cb.onOpenSubMenu(subMenu) : false; } @Override @@ -729,6 +730,10 @@ public class ActionMenuPresenter extends BaseMenuPresenter if (menu instanceof SubMenuBuilder) { ((SubMenuBuilder) menu).getRootMenu().close(false); } + final MenuPresenter.Callback cb = getCallback(); + if (cb != null) { + cb.onCloseMenu(menu, allMenusAreClosing); + } } } diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java index db0d6dd48090..92e9ea6f3c18 100644 --- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java @@ -144,6 +144,10 @@ public abstract class BaseMenuPresenter implements MenuPresenter { mCallback = cb; } + public Callback getCallback() { + return mCallback; + } + /** * Create a new item view that can be re-bound to other item data later. * diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java index 4882adc99266..e1bb3621443e 100644 --- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java @@ -163,7 +163,7 @@ public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClick @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - mMenu.performItemAction(mAdapter.getItem(position), 0); + mMenu.performItemAction(mAdapter.getItem(position), this, 0); } @Override diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index aff697ad580c..195a00d9eca7 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -247,11 +247,17 @@ public class MenuBuilder implements Menu { startDispatchingItemsChanged(); } - private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) { + private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu, + MenuPresenter preferredPresenter) { if (mPresenters.isEmpty()) return false; boolean result = false; + // Try the preferred presenter first. + if (preferredPresenter != null) { + result = preferredPresenter.onSubMenuSelected(subMenu); + } + for (WeakReference<MenuPresenter> ref : mPresenters) { final MenuPresenter presenter = ref.get(); if (presenter == null) { @@ -865,6 +871,10 @@ public class MenuBuilder implements Menu { } public boolean performItemAction(MenuItem item, int flags) { + return performItemAction(item, null, flags); + } + + public boolean performItemAction(MenuItem item, MenuPresenter preferredPresenter, int flags) { MenuItemImpl itemImpl = (MenuItemImpl) item; if (itemImpl == null || !itemImpl.isEnabled()) { @@ -889,7 +899,7 @@ public class MenuBuilder implements Menu { if (providerHasSubMenu) { provider.onPrepareSubMenu(subMenu); } - invoked |= dispatchSubMenuSelected(subMenu); + invoked |= dispatchSubMenuSelected(subMenu, preferredPresenter); if (!invoked) close(true); } else { if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) { diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index b5d74e857807..786f5cfa2e5c 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -526,7 +526,7 @@ public class ActionBarView extends AbsActionBarView { if (mLogoNavItem != null) { mLogoNavItem.setTitle(title); } - mUpGoerFive.setContentDescription(buildHomeContentDescription()); + updateHomeAccessibility(mUpGoerFive.isEnabled()); } public CharSequence getSubtitle() { @@ -544,7 +544,7 @@ public class ActionBarView extends AbsActionBarView { (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle)); mTitleLayout.setVisibility(visible ? VISIBLE : GONE); } - mUpGoerFive.setContentDescription(buildHomeContentDescription()); + updateHomeAccessibility(mUpGoerFive.isEnabled()); } public void setHomeButtonEnabled(boolean enable) { @@ -566,7 +566,11 @@ public class ActionBarView extends AbsActionBarView { mUpGoerFive.setEnabled(enable); mUpGoerFive.setFocusable(enable); // Make sure the home button has an accurate content description for accessibility. - if (!enable) { + updateHomeAccessibility(enable); + } + + private void updateHomeAccessibility(boolean homeEnabled) { + if (!homeEnabled) { mUpGoerFive.setContentDescription(null); mUpGoerFive.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } else { @@ -677,19 +681,7 @@ public class ActionBarView extends AbsActionBarView { } // Make sure the home button has an accurate content description for accessibility. - if (!mHomeLayout.isEnabled()) { - mHomeLayout.setContentDescription(null); - mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - } else { - mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO); - if ((options & ActionBar.DISPLAY_HOME_AS_UP) != 0) { - mHomeLayout.setContentDescription(mContext.getResources().getText( - R.string.action_bar_up_description)); - } else { - mHomeLayout.setContentDescription(mContext.getResources().getText( - R.string.action_bar_home_description)); - } - } + updateHomeAccessibility(mUpGoerFive.isEnabled()); } public void setIcon(Drawable icon) { @@ -1340,11 +1332,13 @@ public class ActionBarView extends AbsActionBarView { public void setHomeActionContentDescription(CharSequence description) { mHomeDescription = description; + updateHomeAccessibility(mUpGoerFive.isEnabled()); } public void setHomeActionContentDescription(int resId) { mHomeDescriptionRes = resId; mHomeDescription = resId != 0 ? getResources().getText(resId) : null; + updateHomeAccessibility(mUpGoerFive.isEnabled()); } static class SavedState extends BaseSavedState { diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index eea9ee14dd0f..212576328764 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -227,7 +227,7 @@ static void ToColor_SI8_Alpha(SkColor dst[], const void* src, int width, do {
*dst++ = SkUnPreMultiply::PMColorToColor(colors[*s++]);
} while (--width != 0);
- ctable->unlockColors(false);
+ ctable->unlockColors();
}
static void ToColor_SI8_Raw(SkColor dst[], const void* src, int width,
@@ -240,7 +240,7 @@ static void ToColor_SI8_Raw(SkColor dst[], const void* src, int width, *dst++ = SkColorSetARGB(SkGetPackedA32(c), SkGetPackedR32(c),
SkGetPackedG32(c), SkGetPackedB32(c));
} while (--width != 0);
- ctable->unlockColors(false);
+ ctable->unlockColors();
}
static void ToColor_SI8_Opaque(SkColor dst[], const void* src, int width,
@@ -253,7 +253,7 @@ static void ToColor_SI8_Opaque(SkColor dst[], const void* src, int width, *dst++ = SkColorSetRGB(SkGetPackedR32(c), SkGetPackedG32(c),
SkGetPackedB32(c));
} while (--width != 0);
- ctable->unlockColors(false);
+ ctable->unlockColors();
}
// can return NULL
@@ -442,9 +442,15 @@ static jboolean Bitmap_hasAlpha(JNIEnv* env, jobject, SkBitmap* bitmap) { return !bitmap->isOpaque();
}
-static void Bitmap_setHasAlpha(JNIEnv* env, jobject, SkBitmap* bitmap,
- jboolean hasAlpha) {
- bitmap->setIsOpaque(!hasAlpha);
+static void Bitmap_setAlphaAndPremultiplied(JNIEnv* env, jobject, SkBitmap* bitmap,
+ jboolean hasAlpha, jboolean isPremul) {
+ if (!hasAlpha) {
+ bitmap->setAlphaType(kOpaque_SkAlphaType);
+ } else if (isPremul) {
+ bitmap->setAlphaType(kPremul_SkAlphaType);
+ } else {
+ bitmap->setAlphaType(kUnpremul_SkAlphaType);
+ }
}
static jboolean Bitmap_hasMipMap(JNIEnv* env, jobject, SkBitmap* bitmap) {
@@ -543,14 +549,14 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, p->writeInt32(bitmap->rowBytes());
p->writeInt32(density);
- if (bitmap->getConfig() == SkBitmap::kIndex8_Config) {
+ if (bitmap->config() == SkBitmap::kIndex8_Config) {
SkColorTable* ctable = bitmap->getColorTable();
if (ctable != NULL) {
int count = ctable->count();
p->writeInt32(count);
memcpy(p->writeInplace(count * sizeof(SkPMColor)),
ctable->lockColors(), count * sizeof(SkPMColor));
- ctable->unlockColors(false);
+ ctable->unlockColors();
} else {
p->writeInt32(0); // indicate no ctable
}
@@ -770,7 +776,7 @@ static JNINativeMethod gBitmapMethods[] = { { "nativeRowBytes", "(I)I", (void*)Bitmap_rowBytes },
{ "nativeConfig", "(I)I", (void*)Bitmap_config },
{ "nativeHasAlpha", "(I)Z", (void*)Bitmap_hasAlpha },
- { "nativeSetHasAlpha", "(IZ)V", (void*)Bitmap_setHasAlpha },
+ { "nativeSetAlphaAndPremultiplied", "(IZZ)V", (void*)Bitmap_setAlphaAndPremultiplied},
{ "nativeHasMipMap", "(I)Z", (void*)Bitmap_hasMipMap },
{ "nativeSetHasMipMap", "(IZ)V", (void*)Bitmap_setHasMipMap },
{ "nativeCreateFromParcel",
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index da6219fcfeb1..709de5c7c070 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -21,6 +21,7 @@ #include <androidfw/Asset.h> #include <androidfw/ResourceTypes.h> #include <netinet/in.h> +#include <stdio.h> #include <sys/mman.h> #include <sys/stat.h> @@ -124,12 +125,18 @@ static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale) { static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStreamRewindable* stream, int sampleSize, bool ditherImage) { + SkImageInfo bitmapInfo; + if (!bitmap->asImageInfo(&bitmapInfo)) { + ALOGW("bitmap has unknown configuration so no memory has been allocated"); + return NULL; + } + SkImageRef* pr; // only use ashmem for large images, since mmaps come at a price if (bitmap->getSize() >= 32 * 1024) { - pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize); + pr = new SkImageRef_ashmem(bitmapInfo, stream, sampleSize); } else { - pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize); + pr = new SkImageRef_GlobalPool(bitmapInfo, stream, sampleSize); } pr->setDitherImage(ditherImage); bitmap->setPixelRef(pr)->unref(); @@ -157,7 +164,7 @@ public: virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { // accounts for scale in final allocation, using eventual size and config const int bytesPerPixel = SkBitmap::ComputeBytesPerPixel( - configForScaledOutput(bitmap->getConfig())); + configForScaledOutput(bitmap->config())); const int requestedSize = bytesPerPixel * int(bitmap->width() * mScale + 0.5f) * int(bitmap->height() * mScale + 0.5f); @@ -191,8 +198,15 @@ public: return false; } + SkImageInfo bitmapInfo; + if (!bitmap->asImageInfo(&bitmapInfo)) { + ALOGW("unable to reuse a bitmap as the target has an unknown bitmap configuration"); + return false; + } + // Create a new pixelref with the new ctable that wraps the previous pixelref - SkPixelRef* pr = new AndroidPixelRef(*static_cast<AndroidPixelRef*>(mPixelRef), ctable); + SkPixelRef* pr = new AndroidPixelRef(*static_cast<AndroidPixelRef*>(mPixelRef), + bitmapInfo, bitmap->rowBytes(), ctable); bitmap->setPixelRef(pr)->unref(); // since we're already allocated, we lockPixels right away @@ -401,8 +415,11 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding // TODO: avoid copying when scaled size equals decodingBitmap size SkBitmap::Config config = configForScaledOutput(decodingBitmap.config()); - outputBitmap->setConfig(config, scaledWidth, scaledHeight); - outputBitmap->setIsOpaque(decodingBitmap.isOpaque()); + // FIXME: If the alphaType is kUnpremul and the image has alpha, the + // colors may not be correct, since Skia does not yet support drawing + // to/from unpremultiplied bitmaps. + outputBitmap->setConfig(config, scaledWidth, scaledHeight, 0, + decodingBitmap.alphaType()); if (!outputBitmap->allocPixels(outputAllocator, NULL)) { return nullObjectReturn("allocation failed for scaled bitmap"); } @@ -414,7 +431,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding } SkPaint paint; - paint.setFilterBitmap(true); + paint.setFilterLevel(SkPaint::kLow_FilterLevel); SkCanvas canvas(*outputBitmap); canvas.scale(sx, sy); @@ -470,6 +487,12 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding bitmapCreateFlags, ninePatchChunk, layoutBounds, -1); } +// Need to buffer enough input to be able to rewind as much as might be read by a decoder +// trying to determine the stream's format. Currently the most is 64, read by +// SkImageDecoder_libwebp. +// FIXME: Get this number from SkImageDecoder +#define BYTES_TO_BUFFER 64 + static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, jobject padding, jobject options) { @@ -477,11 +500,8 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteA SkAutoTUnref<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage)); if (stream.get()) { - // Need to buffer enough input to be able to rewind as much as might be read by a decoder - // trying to determine the stream's format. Currently the most is 64, read by - // SkImageDecoder_libwebp. - // FIXME: Get this number from SkImageDecoder - SkAutoTUnref<SkStreamRewindable> bufferedStream(SkFrontBufferedStream::Create(stream, 64)); + SkAutoTUnref<SkStreamRewindable> bufferedStream( + SkFrontBufferedStream::Create(stream, BYTES_TO_BUFFER)); SkASSERT(bufferedStream.get() != NULL); // for now we don't allow purgeable with java inputstreams bitmap = doDecode(env, bufferedStream, padding, options, false, false); @@ -502,27 +522,48 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fi return nullObjectReturn("fstat return -1"); } - bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions); - bool isShareable = optionsShareable(env, bitmapFactoryOptions); - bool weOwnTheFD = false; - if (isPurgeable && isShareable) { - int newFD = ::dup(descriptor); - if (-1 != newFD) { - weOwnTheFD = true; - descriptor = newFD; - } + // Restore the descriptor's offset on exiting this function. + AutoFDSeek autoRestore(descriptor); + + FILE* file = fdopen(descriptor, "r"); + if (file == NULL) { + return nullObjectReturn("Could not open file"); } - SkAutoTUnref<SkData> data(SkData::NewFromFD(descriptor)); - if (data.get() == NULL) { - return nullObjectReturn("NewFromFD failed in nativeDecodeFileDescriptor"); + SkAutoTUnref<SkFILEStream> fileStream(new SkFILEStream(file, + SkFILEStream::kCallerRetains_Ownership)); + + SkAutoTUnref<SkStreamRewindable> stream; + + // Retain the old behavior of allowing purgeable if both purgeable and + // shareable are set to true. + bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions) + && optionsShareable(env, bitmapFactoryOptions); + if (isPurgeable) { + // Copy the stream, so the image can be decoded multiple times without + // continuing to modify the original file descriptor. + // Copy beginning from the current position. + const size_t fileSize = fileStream->getLength() - fileStream->getPosition(); + void* buffer = sk_malloc_flags(fileSize, 0); + if (buffer == NULL) { + return nullObjectReturn("Could not make a copy for ashmem"); + } + + SkAutoTUnref<SkData> data(SkData::NewFromMalloc(buffer, fileSize)); + + if (fileStream->read(buffer, fileSize) != fileSize) { + return nullObjectReturn("Could not read the file."); + } + + stream.reset(new SkMemoryStream(data)); + } else { + // Use a buffered stream. Although an SkFILEStream can be rewound, this + // ensures that SkImageDecoder::Factory never rewinds beyond the + // current position of the file descriptor. + stream.reset(SkFrontBufferedStream::Create(fileStream, BYTES_TO_BUFFER)); } - SkAutoTUnref<SkMemoryStream> stream(new SkMemoryStream(data)); - /* Allow purgeable iff we own the FD, i.e., in the puregeable and - shareable case. - */ - return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD); + return doDecode(env, stream, padding, bitmapFactoryOptions, isPurgeable); } static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset, @@ -541,7 +582,9 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset, } else { // since we know we'll be done with the asset when we return, we can // just use a simple wrapper - stream = new AssetStreamAdaptor(asset); + stream = new AssetStreamAdaptor(asset, + AssetStreamAdaptor::kNo_OwnAsset, + AssetStreamAdaptor::kNo_HasMemoryBase); } SkAutoUnref aur(stream); return doDecode(env, stream, padding, options, forcePurgeable, forcePurgeable); diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index ee47ac46ecd1..1412a0e900b3 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -29,7 +29,6 @@ #include "CreateJavaOutputStreamAdaptor.h" #include "Utils.h" #include "JNIHelp.h" -#include "SkTScopedPtr.h" #include <android_runtime/AndroidRuntime.h> #include "android_util_Binder.h" @@ -76,7 +75,7 @@ private: int fHeight; }; -static jobject createBitmapRegionDecoder(JNIEnv* env, SkStream* stream) { +static jobject createBitmapRegionDecoder(JNIEnv* env, SkStreamRewindable* stream) { SkImageDecoder* decoder = SkImageDecoder::Factory(stream); int width, height; if (NULL == decoder) { @@ -108,7 +107,7 @@ static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray b For now we just always copy the array's data if isShareable. */ AutoJavaByteArray ar(env, byteArray); - SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true); + SkStreamRewindable* stream = new SkMemoryStream(ar.ptr() + offset, length, true); jobject brd = createBitmapRegionDecoder(env, stream); SkSafeUnref(stream); // the decoder now holds a reference @@ -215,7 +214,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b region.fRight = start_x + width; region.fBottom = start_y + height; SkBitmap* bitmap = NULL; - SkTScopedPtr<SkBitmap> adb; + SkAutoTDelete<SkBitmap> adb; if (tileBitmap != NULL) { // Re-use bitmap. @@ -246,7 +245,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b } // detach bitmap from its autodeleter, since we want to own it now - adb.release(); + adb.detach(); JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator(); jbyteArray buff = allocator->getStorageObjAndReset(); diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 813dd5af1e28..edf3b4a052a4 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -476,7 +476,7 @@ public: if (paint) { filteredPaint = *paint; } - filteredPaint.setFilterBitmap(true); + filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel); canvas->drawBitmap(*bitmap, left_, top_, &filteredPaint); } else { canvas->drawBitmap(*bitmap, left_, top_, paint); @@ -491,7 +491,7 @@ public: if (paint) { filteredPaint = *paint; } - filteredPaint.setFilterBitmap(true); + filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel); canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint); @@ -514,7 +514,7 @@ public: if (paint) { filteredPaint = *paint; } - filteredPaint.setFilterBitmap(true); + filteredPaint.setFilterLevel(SkPaint::kLow_FilterLevel); canvas->drawBitmapRect(*bitmap, srcPtr, dst, &filteredPaint); } else { canvas->drawBitmapRect(*bitmap, srcPtr, dst, paint); diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 8cb152db06e3..3090ad2ff711 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -346,6 +346,18 @@ SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region) /////////////////////////////////////////////////////////////////////////////////////////// +// Assert that bitmap's SkAlphaType is consistent with isPremultiplied. +static void assert_premultiplied(const SkBitmap& bitmap, bool isPremultiplied) { + // kOpaque_SkAlphaType and kIgnore_SkAlphaType mean that isPremultiplied is + // irrelevant. This just tests to ensure that the SkAlphaType is not + // opposite of isPremultiplied. + if (isPremultiplied) { + SkASSERT(bitmap.alphaType() != kUnpremul_SkAlphaType); + } else { + SkASSERT(bitmap.alphaType() != kPremul_SkAlphaType); + } +} + jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density) { @@ -354,6 +366,10 @@ jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buff bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable; bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied; + // The caller needs to have already set the alpha type properly, so the + // native SkBitmap stays in sync with the Java Bitmap. + assert_premultiplied(*bitmap, isPremultiplied); + jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)), buffer, bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied, @@ -371,6 +387,10 @@ jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreat void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap, bool isPremultiplied) { + // The caller needs to have already set the alpha type properly, so the + // native SkBitmap stays in sync with the Java Bitmap. + assert_premultiplied(*bitmap, isPremultiplied); + env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID, bitmap->width(), bitmap->height(), isPremultiplied); } @@ -413,8 +433,9 @@ static JNIEnv* vm2env(JavaVM* vm) /////////////////////////////////////////////////////////////////////////////// -AndroidPixelRef::AndroidPixelRef(JNIEnv* env, void* storage, size_t size, jbyteArray storageObj, - SkColorTable* ctable) : SkMallocPixelRef(storage, size, ctable, (storageObj == NULL)), +AndroidPixelRef::AndroidPixelRef(JNIEnv* env, const SkImageInfo& info, void* storage, + size_t rowBytes, jbyteArray storageObj, SkColorTable* ctable) : + SkMallocPixelRef(info, storage, rowBytes, ctable, (storageObj == NULL)), fWrappedPixelRef(NULL) { SkASSERT(storage); SkASSERT(env); @@ -429,13 +450,13 @@ AndroidPixelRef::AndroidPixelRef(JNIEnv* env, void* storage, size_t size, jbyteA // If storageObj is NULL, the memory was NOT allocated on the Java heap fOnJavaHeap = (storageObj != NULL); - } -AndroidPixelRef::AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, SkColorTable* ctable) : - SkMallocPixelRef(wrappedPixelRef.getAddr(), wrappedPixelRef.getSize(), ctable, false), +AndroidPixelRef::AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, const SkImageInfo& info, + size_t rowBytes, SkColorTable* ctable) : + SkMallocPixelRef(info, wrappedPixelRef.getAddr(), rowBytes, ctable, false), fWrappedPixelRef(wrappedPixelRef.fWrappedPixelRef ? - wrappedPixelRef.fWrappedPixelRef : &wrappedPixelRef) + wrappedPixelRef.fWrappedPixelRef : &wrappedPixelRef) { SkASSERT(fWrappedPixelRef); SkSafeRef(fWrappedPixelRef); @@ -540,13 +561,21 @@ jbyteArray GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, return NULL; } + SkImageInfo bitmapInfo; + if (!bitmap->asImageInfo(&bitmapInfo)) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "unknown bitmap configuration"); + return NULL; + } + size_t size = size64.get32(); jbyteArray arrayObj = env->NewByteArray(size); if (arrayObj) { // TODO: make this work without jniGetNonMovableArrayElements jbyte* addr = jniGetNonMovableArrayElements(&env->functions, arrayObj); if (addr) { - SkPixelRef* pr = new AndroidPixelRef(env, (void*) addr, size, arrayObj, ctable); + SkPixelRef* pr = new AndroidPixelRef(env, bitmapInfo, (void*) addr, + bitmap->rowBytes(), arrayObj, ctable); bitmap->setPixelRef(pr)->unref(); // since we're already allocated, we lockPixels right away // HeapAllocator behaves this way too diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index f4590b956d78..cb154aaf28e5 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -57,6 +57,7 @@ public: /** Create a java Bitmap object given the native bitmap (required) and optional storage array (may be null). + bitmap's SkAlphaType must already be in sync with bitmapCreateFlags. */ static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density = -1); @@ -64,6 +65,9 @@ public: static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags, jbyteArray ninepatch, int density = -1); + /** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in + sync with isPremultiplied + */ static void reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap, bool isPremultiplied); @@ -88,15 +92,16 @@ public: class AndroidPixelRef : public SkMallocPixelRef { public: - AndroidPixelRef(JNIEnv* env, void* storage, size_t size, jbyteArray storageObj, - SkColorTable* ctable); + AndroidPixelRef(JNIEnv* env, const SkImageInfo& info, void* storage, size_t rowBytes, + jbyteArray storageObj, SkColorTable* ctable); /** * Creates an AndroidPixelRef that wraps (and refs) another to reuse/share * the same storage and java byte array refcounting, yet have a different * color table. */ - AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, SkColorTable* ctable); + AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, const SkImageInfo& info, + size_t rowBytes, SkColorTable* ctable); virtual ~AndroidPixelRef(); diff --git a/core/jni/android/graphics/Movie.cpp b/core/jni/android/graphics/Movie.cpp index feb2decd3528..55be7c194f12 100644 --- a/core/jni/android/graphics/Movie.cpp +++ b/core/jni/android/graphics/Movie.cpp @@ -85,7 +85,9 @@ static void movie_draw(JNIEnv* env, jobject movie, jobject canvas, static jobject movie_decodeAsset(JNIEnv* env, jobject clazz, jint native_asset) { android::Asset* asset = reinterpret_cast<android::Asset*>(native_asset); if (asset == NULL) return NULL; - SkAutoTUnref<SkStreamRewindable> stream (new android::AssetStreamAdaptor(asset)); + SkAutoTUnref<SkStreamRewindable> stream (new android::AssetStreamAdaptor(asset, + android::AssetStreamAdaptor::kNo_OwnAsset, + android::AssetStreamAdaptor::kNo_HasMemoryBase)); SkMovie* moov = SkMovie::DecodeStream(stream.get()); return create_jmovie(env, moov); } diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp index df996afd0bf7..d3482da34fa2 100644 --- a/core/jni/android/graphics/NinePatchPeeker.cpp +++ b/core/jni/android/graphics/NinePatchPeeker.cpp @@ -40,15 +40,14 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) { // now update our host to force index or 32bit config // 'cause we don't want 565 predithered, since as a 9patch, we know // we will be stretched, and therefore we want to dither afterwards. - static const SkBitmap::Config gNo565Pref[] = { - SkBitmap::kIndex8_Config, - SkBitmap::kIndex8_Config, - SkBitmap::kARGB_8888_Config, - SkBitmap::kARGB_8888_Config, - SkBitmap::kARGB_8888_Config, - SkBitmap::kARGB_8888_Config, - }; - fHost->setPrefConfigTable(gNo565Pref); + SkImageDecoder::PrefConfigTable table; + table.fPrefFor_8Index_NoAlpha_src = SkBitmap::kIndex8_Config; + table.fPrefFor_8Index_YesAlpha_src = SkBitmap::kIndex8_Config; + table.fPrefFor_8Gray_src = SkBitmap::kARGB_8888_Config; + table.fPrefFor_8bpc_NoAlpha_src = SkBitmap::kARGB_8888_Config; + table.fPrefFor_8bpc_YesAlpha_src = SkBitmap::kARGB_8888_Config; + + fHost->setPrefConfigTable(table); } else if (strcmp("npLb", tag) == 0 && length == sizeof(int) * 4) { fLayoutBounds = new int[4]; memcpy(fLayoutBounds, data, sizeof(int) * 4); diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 40e0731cbee4..fd3f327666cb 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -148,7 +148,8 @@ public: static void setFilterBitmap(JNIEnv* env, jobject paint, jboolean filterBitmap) { NPE_CHECK_RETURN_VOID(env, paint); - GraphicsJNI::getNativePaint(env, paint)->setFilterBitmap(filterBitmap); + GraphicsJNI::getNativePaint(env, paint)->setFilterLevel( + filterBitmap ? SkPaint::kLow_FilterLevel : SkPaint::kNone_FilterLevel); } static void setDither(JNIEnv* env, jobject paint, jboolean dither) { @@ -565,7 +566,7 @@ public: return 0; } } - jfloat advancesArray[count]; + jfloat* advancesArray = new jfloat[count]; jfloat totalAdvance = 0; TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags, @@ -574,6 +575,7 @@ public: if (advances != NULL) { env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray); } + delete [] advancesArray; return totalAdvance; } diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp index ded21860b8b9..f0a7baf8cb04 100644 --- a/core/jni/android/graphics/Region.cpp +++ b/core/jni/android/graphics/Region.cpp @@ -177,7 +177,7 @@ static SkRegion* Region_createFromParcel(JNIEnv* env, jobject clazz, jobject par SkRegion* region = new SkRegion; size_t size = p->readInt32(); - region->readFromMemory(p->readInplace(size)); + region->readFromMemory(p->readInplace(size), size); return region; } diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp index 92d253f0458e..144ac3990cd2 100644 --- a/core/jni/android/graphics/TextLayoutCache.cpp +++ b/core/jni/android/graphics/TextLayoutCache.cpp @@ -20,6 +20,7 @@ #include "TextLayoutCache.h" #include "TextLayout.h" +#include "SkGlyphCache.h" #include "SkTypeface_android.h" #include "HarfBuzzNGFaceSkia.h" #include <unicode/unistr.h> @@ -757,8 +758,8 @@ void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* conte outPos->add(ypos); totalAdvance += xAdvance; - // TODO: consider using glyph cache - const SkGlyph& metrics = mShapingPaint.getGlyphMetrics(glyphId, NULL); + SkAutoGlyphCache autoCache(mShapingPaint, NULL, NULL); + const SkGlyph& metrics = autoCache.getCache()->getGlyphIDMetrics(glyphId); outBounds->join(xpos + metrics.fLeft, ypos + metrics.fTop, xpos + metrics.fLeft + metrics.fWidth, ypos + metrics.fTop + metrics.fHeight); diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp index a7a0bb222cdf..d10a960049f1 100644 --- a/core/jni/android/graphics/Typeface.cpp +++ b/core/jni/android/graphics/Typeface.cpp @@ -4,6 +4,7 @@ #include "GraphicsJNI.h" #include "SkStream.h" #include "SkTypeface.h" +#include "Utils.h" #include <android_runtime/android_util_AssetManager.h> #include <androidfw/AssetManager.h> @@ -34,6 +35,13 @@ static SkTypeface* Typeface_create(JNIEnv* env, jobject, jstring name, if (NULL != name) { AutoJavaStringToUTF8 str(env, name); face = SkTypeface::CreateFromName(str.c_str(), style); + // Try to find the closest matching font, using the standard heuristic + if (NULL == face) { + face = SkTypeface::CreateFromName(str.c_str(), (SkTypeface::Style)(style ^ SkTypeface::kItalic)); + } + for (int i = 0; NULL == face && i < 4; i++) { + face = SkTypeface::CreateFromName(str.c_str(), (SkTypeface::Style)i); + } } // return the default font at the best style if no exact match exists @@ -45,8 +53,13 @@ static SkTypeface* Typeface_create(JNIEnv* env, jobject, jstring name, static SkTypeface* Typeface_createFromTypeface(JNIEnv* env, jobject, SkTypeface* family, int style) { SkTypeface* face = SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)style); - // return the default font at the best style if the requested style does not - // exist in the provided family + // Try to find the closest matching font, using the standard heuristic + if (NULL == face) { + face = SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic)); + } + for (int i = 0; NULL == face && i < 4; i++) { + face = SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)i); + } if (NULL == face) { face = SkTypeface::CreateFromName(NULL, (SkTypeface::Style)style); } @@ -61,65 +74,6 @@ static int Typeface_getStyle(JNIEnv* env, jobject obj, SkTypeface* face) { return face->style(); } -class AssetStream : public SkStream { -public: - AssetStream(Asset* asset, bool hasMemoryBase) : fAsset(asset) - { - fMemoryBase = hasMemoryBase ? fAsset->getBuffer(false) : NULL; - } - - virtual ~AssetStream() - { - delete fAsset; - } - - virtual const void* getMemoryBase() - { - return fMemoryBase; - } - - virtual bool rewind() - { - off64_t pos = fAsset->seek(0, SEEK_SET); - return pos != (off64_t)-1; - } - - virtual size_t read(void* buffer, size_t size) - { - ssize_t amount; - - if (NULL == buffer) - { - if (0 == size) // caller is asking us for our total length - return fAsset->getLength(); - - // asset->seek returns new total offset - // we want to return amount that was skipped - - off64_t oldOffset = fAsset->seek(0, SEEK_CUR); - if (-1 == oldOffset) - return 0; - off64_t newOffset = fAsset->seek(size, SEEK_CUR); - if (-1 == newOffset) - return 0; - - amount = newOffset - oldOffset; - } - else - { - amount = fAsset->read(buffer, size); - } - - if (amount < 0) - amount = 0; - return amount; - } - -private: - Asset* fAsset; - const void* fMemoryBase; -}; - static SkTypeface* Typeface_createFromAsset(JNIEnv* env, jobject, jobject jassetMgr, jstring jpath) { @@ -138,7 +92,9 @@ static SkTypeface* Typeface_createFromAsset(JNIEnv* env, jobject, return NULL; } - SkStream* stream = new AssetStream(asset, true); + SkStream* stream = new AssetStreamAdaptor(asset, + AssetStreamAdaptor::kYes_OwnAsset, + AssetStreamAdaptor::kYes_HasMemoryBase); SkTypeface* face = SkTypeface::CreateFromStream(stream); // SkTypeFace::CreateFromStream calls ref() on the stream, so we // need to unref it here or it won't be freed later on diff --git a/core/jni/android/graphics/Utils.cpp b/core/jni/android/graphics/Utils.cpp index b7d1f3ab220f..eb416cbe7ad2 100644 --- a/core/jni/android/graphics/Utils.cpp +++ b/core/jni/android/graphics/Utils.cpp @@ -19,6 +19,21 @@ using namespace android; +AssetStreamAdaptor::AssetStreamAdaptor(Asset* asset, OwnAsset ownAsset, + HasMemoryBase hasMemoryBase) + : fAsset(asset) + , fMemoryBase(kYes_HasMemoryBase == hasMemoryBase ? + asset->getBuffer(false) : NULL) + , fOwnAsset(ownAsset) +{ +} + +AssetStreamAdaptor::~AssetStreamAdaptor() { + if (kYes_OwnAsset == fOwnAsset) { + delete fAsset; + } +} + bool AssetStreamAdaptor::rewind() { off64_t pos = fAsset->seek(0, SEEK_SET); if (pos == (off64_t)-1) { diff --git a/core/jni/android/graphics/Utils.h b/core/jni/android/graphics/Utils.h index a1ac72a0b92b..b90593cdc4b0 100644 --- a/core/jni/android/graphics/Utils.h +++ b/core/jni/android/graphics/Utils.h @@ -28,16 +28,36 @@ namespace android { class AssetStreamAdaptor : public SkStreamRewindable { public: - AssetStreamAdaptor(Asset* a) : fAsset(a) {} + // Enum passed to constructor. If set to kYes_OwnAsset, + // the passed in Asset will be deleted upon destruction. + enum OwnAsset { + kYes_OwnAsset, + kNo_OwnAsset, + }; + + // Enum passed to constructor. If set to kYes_HasMemoryBase, + // getMemoryBase will return the Asset's buffer. + enum HasMemoryBase { + kYes_HasMemoryBase, + kNo_HasMemoryBase, + }; + + AssetStreamAdaptor(Asset*, OwnAsset, HasMemoryBase); + ~AssetStreamAdaptor(); + virtual bool rewind(); virtual size_t read(void* buffer, size_t size); virtual bool hasLength() const { return true; } virtual size_t getLength() const; virtual bool isAtEnd() const; + virtual const void* getMemoryBase() { return fMemoryBase; } + virtual SkStreamRewindable* duplicate() const; private: - Asset* fAsset; + Asset* fAsset; + const void* const fMemoryBase; + const OwnAsset fOwnAsset; }; /** diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp index b57a0fe8aa10..6175a8f81369 100644 --- a/core/jni/android/graphics/pdf/PdfDocument.cpp +++ b/core/jni/android/graphics/pdf/PdfDocument.cpp @@ -17,62 +17,138 @@ #include "jni.h" #include "GraphicsJNI.h" #include <android_runtime/AndroidRuntime.h> +#include <vector> + +#include "CreateJavaOutputStreamAdaptor.h" #include "SkCanvas.h" -#include "SkPDFDevice.h" -#include "SkPDFDocument.h" +#include "SkDocument.h" +#include "SkPicture.h" +#include "SkStream.h" #include "SkRect.h" -#include "SkSize.h" -#include "CreateJavaOutputStreamAdaptor.h" -#include "JNIHelp.h" namespace android { -#define LOGD(x...) do { Log::Instance()->printf(Log::ELogD, x); } while(0) +struct PageRecord { -static jint nativeCreateDocument(JNIEnv* env, jobject clazz) { - return reinterpret_cast<jint>(new SkPDFDocument()); -} + PageRecord(int width, int height, const SkRect& contentRect) + : mPicture(new SkPicture()), mWidth(width), mHeight(height) { + mContentRect = contentRect; + } -static void nativeFinalize(JNIEnv* env, jobject thiz, jint documentPtr) { - delete reinterpret_cast<SkPDFDocument*>(documentPtr); -} + ~PageRecord() { + mPicture->unref(); + } -static jint nativeCreatePage(JNIEnv* env, jobject thiz, jint pageWidth, jint pageHeight, - jint contentLeft, jint contentTop, jint contentRight, jint contentBottom) { + SkPicture* const mPicture; + const int mWidth; + const int mHeight; + SkRect mContentRect; +}; + +class PdfDocument { +public: + PdfDocument() { + mCurrentPage = NULL; + } + + SkCanvas* startPage(int width, int height, + int contentLeft, int contentTop, int contentRight, int contentBottom) { + assert(mCurrentPage == NULL); + + SkRect contentRect = SkRect::MakeLTRB( + contentLeft, contentTop, contentRight, contentBottom); + PageRecord* page = new PageRecord(width, height, contentRect); + mPages.push_back(page); + mCurrentPage = page; + + SkCanvas* canvas = page->mPicture->beginRecording( + contentRect.width(), contentRect.height(), 0); + + // We pass this canvas to Java where it is used to construct + // a Java Canvas object which dereferences the pointer when it + // is destroyed, so we have to bump up the reference count. + canvas->ref(); + + return canvas; + } - SkMatrix transformation; - transformation.setTranslate(contentLeft, contentTop); + void finishPage() { + assert(mCurrentPage != NULL); + mCurrentPage->mPicture->endRecording(); + mCurrentPage = NULL; + } - SkISize skPageSize = SkISize::Make(pageWidth, pageHeight); - SkISize skContentSize = SkISize::Make(contentRight - contentLeft, contentBottom - contentTop); + void write(SkWStream* stream) { + SkDocument* document = SkDocument::CreatePDF(stream); + for (unsigned i = 0; i < mPages.size(); i++) { + PageRecord* page = mPages[i]; - SkPDFDevice* skPdfDevice = new SkPDFDevice(skPageSize, skContentSize, transformation); - return reinterpret_cast<jint>(new SkCanvas(skPdfDevice)); + SkCanvas* canvas = document->beginPage(page->mWidth, page->mHeight, + &(page->mContentRect)); + + canvas->clipRect(page->mContentRect); + canvas->translate(page->mContentRect.left(), page->mContentRect.top()); + canvas->drawPicture(*page->mPicture); + + document->endPage(); + } + document->close(); + } + + void close() { + for (unsigned i = 0; i < mPages.size(); i++) { + delete mPages[i]; + } + delete mCurrentPage; + mCurrentPage = NULL; + } + +private: + ~PdfDocument() { + close(); + } + + std::vector<PageRecord*> mPages; + PageRecord* mCurrentPage; +}; + +static jint nativeCreateDocument(JNIEnv* env, jobject thiz) { + return reinterpret_cast<jint>(new PdfDocument()); +} + +static jint nativeStartPage(JNIEnv* env, jobject thiz, jint documentPtr, + jint pageWidth, jint pageHeight, + jint contentLeft, jint contentTop, jint contentRight, jint contentBottom) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + return reinterpret_cast<jint>(document->startPage(pageWidth, pageHeight, + contentLeft, contentTop, contentRight, contentBottom)); } -static void nativeAppendPage(JNIEnv* env, jobject thiz, jint documentPtr, jint pagePtr) { - SkCanvas* page = reinterpret_cast<SkCanvas*>(pagePtr); - SkPDFDocument* document = reinterpret_cast<SkPDFDocument*>(documentPtr); - SkPDFDevice* device = static_cast<SkPDFDevice*>(page->getDevice()); - document->appendPage(device); +static void nativeFinishPage(JNIEnv* env, jobject thiz, jint documentPtr) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + document->finishPage(); } -static void nativeWriteTo(JNIEnv* env, jobject clazz, jint documentPtr, - jobject out, jbyteArray chunk) { +static void nativeWriteTo(JNIEnv* env, jobject thiz, jint documentPtr, jobject out, + jbyteArray chunk) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); SkWStream* skWStream = CreateJavaOutputStreamAdaptor(env, out, chunk); - SkPDFDocument* document = reinterpret_cast<SkPDFDocument*>(documentPtr); - document->emitPDF(skWStream); + document->write(skWStream); delete skWStream; } +static void nativeClose(JNIEnv* env, jobject thiz, jint documentPtr) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + document->close(); +} + static JNINativeMethod gPdfDocument_Methods[] = { {"nativeCreateDocument", "()I", (void*) nativeCreateDocument}, - {"nativeFinalize", "(I)V", (void*) nativeFinalize}, - {"nativeCreatePage", "(IIIIII)I", - (void*) nativeCreatePage}, - {"nativeAppendPage", "(II)V", (void*) nativeAppendPage}, - {"nativeWriteTo", "(ILjava/io/OutputStream;[B)V", (void*) nativeWriteTo} + {"nativeStartPage", "(IIIIIII)I", (void*) nativeStartPage}, + {"nativeFinishPage", "(I)V", (void*) nativeFinishPage}, + {"nativeWriteTo", "(ILjava/io/OutputStream;[B)V", (void*) nativeWriteTo}, + {"nativeClose", "(I)V", (void*) nativeClose} }; int register_android_graphics_pdf_PdfDocument(JNIEnv* env) { diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp index 44af1993dc3d..4bb091d1ab6e 100644 --- a/core/jni/android/opengl/util.cpp +++ b/core/jni/android/opengl/util.cpp @@ -632,7 +632,7 @@ static jint util_getInternalFormat(JNIEnv *env, jclass clazz, SkBitmap const * nativeBitmap = (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID); const SkBitmap& bitmap(*nativeBitmap); - SkBitmap::Config config = bitmap.getConfig(); + SkBitmap::Config config = bitmap.config(); return getInternalFormat(config); } @@ -642,7 +642,7 @@ static jint util_getType(JNIEnv *env, jclass clazz, SkBitmap const * nativeBitmap = (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID); const SkBitmap& bitmap(*nativeBitmap); - SkBitmap::Config config = bitmap.getConfig(); + SkBitmap::Config config = bitmap.config(); return getType(config); } @@ -653,7 +653,7 @@ static jint util_texImage2D(JNIEnv *env, jclass clazz, SkBitmap const * nativeBitmap = (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID); const SkBitmap& bitmap(*nativeBitmap); - SkBitmap::Config config = bitmap.getConfig(); + SkBitmap::Config config = bitmap.config(); if (internalformat < 0) { internalformat = getInternalFormat(config); } @@ -681,7 +681,7 @@ static jint util_texImage2D(JNIEnv *env, jclass clazz, SkColorTable* ctable = bitmap.getColorTable(); memcpy(data, ctable->lockColors(), ctable->count() * sizeof(SkPMColor)); memcpy(pixels, p, size); - ctable->unlockColors(false); + ctable->unlockColors(); glCompressedTexImage2D(target, level, internalformat, w, h, border, imageSize, data); free(data); } else { @@ -702,7 +702,7 @@ static jint util_texSubImage2D(JNIEnv *env, jclass clazz, SkBitmap const * nativeBitmap = (SkBitmap const *)env->GetIntField(jbitmap, nativeBitmapID); const SkBitmap& bitmap(*nativeBitmap); - SkBitmap::Config config = bitmap.getConfig(); + SkBitmap::Config config = bitmap.config(); if (format < 0) { format = getInternalFormat(config); if (format == GL_PALETTE8_RGBA8_OES) diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp index 1fe4b08d9721..5b0a4b2e4039 100644 --- a/core/jni/android_opengl_EGL14.cpp +++ b/core/jni/android_opengl_EGL14.cpp @@ -630,7 +630,7 @@ not_valid_surface: if (producer == NULL) goto not_valid_surface; - window = new android::Surface(producer); + window = new android::Surface(producer, true); if (window == NULL) goto not_valid_surface; diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 1868469f8ebb..dd178d8297f3 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -39,6 +39,7 @@ #include <SkCanvas.h> #include <SkBitmap.h> +#include <SkImage.h> #include <SkRegion.h> #include <utils/misc.h> @@ -178,7 +179,8 @@ static jboolean nativeIsConsumerRunningBehind(JNIEnv* env, jclass clazz, jint na static inline SkBitmap::Config convertPixelFormat(PixelFormat format) { /* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then we can map to SkBitmap::kARGB_8888_Config, and optionally call - bitmap.setIsOpaque(true) on the resulting SkBitmap (as an accelerator) + bitmap.setAlphaType(kOpaque_SkAlphaType) on the resulting SkBitmap + (as an accelerator) */ switch (format) { case PIXEL_FORMAT_RGBX_8888: return SkBitmap::kARGB_8888_Config; @@ -234,7 +236,7 @@ static jint nativeLockCanvas(JNIEnv* env, jclass clazz, ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format); bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr); if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) { - bitmap.setIsOpaque(true); + bitmap.setAlphaType(kOpaque_SkAlphaType); } if (outBuffer.width > 0 && outBuffer.height > 0) { bitmap.setPixels(outBuffer.bits); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 67eade83cdc4..b8d3c20fd899 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -61,51 +61,21 @@ static struct { class ScreenshotPixelRef : public SkPixelRef { public: - ScreenshotPixelRef(SkColorTable* ctable) { - fCTable = ctable; - SkSafeRef(ctable); + ScreenshotPixelRef(const SkImageInfo& info, ScreenshotClient* screenshot) : + SkPixelRef(info), + mScreenshot(screenshot) { setImmutable(); } virtual ~ScreenshotPixelRef() { - SkSafeUnref(fCTable); - } - - status_t update(const sp<IBinder>& display, int width, int height, - int minLayer, int maxLayer, bool allLayers) { - status_t res = (width > 0 && height > 0) - ? (allLayers - ? mScreenshot.update(display, width, height) - : mScreenshot.update(display, width, height, minLayer, maxLayer)) - : mScreenshot.update(display); - if (res != NO_ERROR) { - return res; - } - - return NO_ERROR; - } - - uint32_t getWidth() const { - return mScreenshot.getWidth(); - } - - uint32_t getHeight() const { - return mScreenshot.getHeight(); - } - - uint32_t getStride() const { - return mScreenshot.getStride(); - } - - uint32_t getFormat() const { - return mScreenshot.getFormat(); + delete mScreenshot; } protected: // overrides from SkPixelRef virtual void* onLockPixels(SkColorTable** ct) { - *ct = fCTable; - return (void*)mScreenshot.getPixels(); + *ct = NULL; + return (void*)mScreenshot->getPixels(); } virtual void onUnlockPixels() { @@ -113,8 +83,7 @@ protected: SK_DECLARE_UNFLATTENABLE_OBJECT() private: - ScreenshotClient mScreenshot; - SkColorTable* fCTable; + ScreenshotClient* mScreenshot; typedef SkPixelRef INHERITED; }; @@ -147,19 +116,6 @@ static void nativeDestroy(JNIEnv* env, jclass clazz, jint nativeObject) { ctrl->decStrong((void *)nativeCreate); } -static inline SkBitmap::Config convertPixelFormat(PixelFormat format) { - /* note: if PIXEL_FORMAT_RGBX_8888 means that all alpha bytes are 0xFF, then - we can map to SkBitmap::kARGB_8888_Config, and optionally call - bitmap.setIsOpaque(true) on the resulting SkBitmap (as an accelerator) - */ - switch (format) { - case PIXEL_FORMAT_RGBX_8888: return SkBitmap::kARGB_8888_Config; - case PIXEL_FORMAT_RGBA_8888: return SkBitmap::kARGB_8888_Config; - case PIXEL_FORMAT_RGB_565: return SkBitmap::kRGB_565_Config; - default: return SkBitmap::kNo_Config; - } -} - static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject displayTokenObj, jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) { sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj); @@ -167,26 +123,50 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject display return NULL; } - ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL); - if (pixels->update(displayToken, width, height, - minLayer, maxLayer, allLayers) != NO_ERROR) { - delete pixels; + ScreenshotClient* screenshot = new ScreenshotClient(); + status_t res = (width > 0 && height > 0) + ? (allLayers + ? screenshot->update(displayToken, width, height) + : screenshot->update(displayToken, width, height, minLayer, maxLayer)) + : screenshot->update(displayToken); + if (res != NO_ERROR) { + delete screenshot; return NULL; } - uint32_t w = pixels->getWidth(); - uint32_t h = pixels->getHeight(); - uint32_t s = pixels->getStride(); - uint32_t f = pixels->getFormat(); - ssize_t bpr = s * android::bytesPerPixel(f); + SkImageInfo screenshotInfo; + screenshotInfo.fWidth = screenshot->getWidth(); + screenshotInfo.fHeight = screenshot->getHeight(); - SkBitmap* bitmap = new SkBitmap(); - bitmap->setConfig(convertPixelFormat(f), w, h, bpr); - if (f == PIXEL_FORMAT_RGBX_8888) { - bitmap->setIsOpaque(true); + switch (screenshot->getFormat()) { + case PIXEL_FORMAT_RGBX_8888: { + screenshotInfo.fColorType = kRGBA_8888_SkColorType; + screenshotInfo.fAlphaType = kIgnore_SkAlphaType; + break; + } + case PIXEL_FORMAT_RGBA_8888: { + screenshotInfo.fColorType = kRGBA_8888_SkColorType; + screenshotInfo.fAlphaType = kPremul_SkAlphaType; + break; + } + case PIXEL_FORMAT_RGB_565: { + screenshotInfo.fColorType = kRGB_565_SkColorType; + screenshotInfo.fAlphaType = kIgnore_SkAlphaType; + break; + } + default: { + delete screenshot; + return NULL; + } } - if (w > 0 && h > 0) { + // takes ownership of ScreenshotClient + ScreenshotPixelRef* pixels = new ScreenshotPixelRef(screenshotInfo, screenshot); + ssize_t rowBytes = screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat()); + + SkBitmap* bitmap = new SkBitmap(); + bitmap->setConfig(screenshotInfo, rowBytes); + if (screenshotInfo.fWidth > 0 && screenshotInfo.fHeight > 0) { bitmap->setPixelRef(pixels)->unref(); bitmap->lockPixels(); } else { diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index 0f4290059b89..7a4a20a97cab 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -27,6 +27,7 @@ #include <SkBitmap.h> #include <SkCanvas.h> +#include <SkImage.h> namespace android { @@ -156,7 +157,7 @@ static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, bitmap.setConfig(convertPixelFormat(buffer.format), buffer.width, buffer.height, bytesCount); if (buffer.format == WINDOW_FORMAT_RGBX_8888) { - bitmap.setIsOpaque(true); + bitmap.setAlphaType(kOpaque_SkAlphaType); } if (buffer.width > 0 && buffer.height > 0) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e8f0ab796e69..ca311096c126 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1056,8 +1056,9 @@ android:permissionGroupFlags="personalInfo" android:priority="370" /> - <!-- Allows an application to modify or abort outgoing - calls. --> + <!-- Allows an application to see the number being dialed during an outgoing + call with the option to redirect the call to a different number or + abort the call altogether. --> <permission android:name="android.permission.PROCESS_OUTGOING_CALLS" android:permissionGroup="android.permission-group.PHONE_CALLS" android:protectionLevel="dangerous" @@ -1191,7 +1192,7 @@ android:permissionGroup="android.permission-group.STORAGE" android:label="@string/permlab_manageDocs" android:description="@string/permdesc_manageDocs" - android:protectionLevel="signature|system" /> + android:protectionLevel="signature" /> <!-- ================================== --> <!-- Permissions for screenlock --> @@ -1526,7 +1527,7 @@ @hide --> <permission android:name="android.permission.FORCE_STOP_PACKAGES" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" - android:protectionLevel="signature" + android:protectionLevel="signature|system" android:label="@string/permlab_forceStopPackages" android:description="@string/permdesc_forceStopPackages" /> @@ -1992,6 +1993,14 @@ android:description="@string/permdesc_bindWallpaper" android:protectionLevel="signature|system" /> + <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider}, + to ensure that only the system can bind to it. + @hide --> + <permission android:name="android.permission.BIND_REMOTE_DISPLAY" + android:label="@string/permlab_bindRemoteDisplay" + android:description="@string/permdesc_bindRemoteDisplay" + android:protectionLevel="signature" /> + <!-- Must be required by device administration receiver, to ensure that only the system can interact with it. --> <permission android:name="android.permission.BIND_DEVICE_ADMIN" @@ -2681,6 +2690,15 @@ <service android:name="android.hardware.location.GeofenceHardwareService" android:permission="android.permission.LOCATION_HARDWARE" android:exported="false" /> + + <service android:name="com.android.internal.backup.LocalTransportService" + android:permission="android.permission.CONFIRM_FULL_BACKUP" + android:exported="false"> + <intent-filter> + <action android:name="android.backup.TRANSPORT_HOST" /> + </intent-filter> + </service> + </application> </manifest> diff --git a/core/res/res/drawable-hdpi/ic_media_group_collapse.png b/core/res/res/drawable-hdpi/ic_media_group_collapse.png Binary files differdeleted file mode 100644 index 89abf2c21ac5..000000000000 --- a/core/res/res/drawable-hdpi/ic_media_group_collapse.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/ic_media_group_expand.png b/core/res/res/drawable-hdpi/ic_media_group_expand.png Binary files differdeleted file mode 100644 index d9470b2ef098..000000000000 --- a/core/res/res/drawable-hdpi/ic_media_group_expand.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png Binary files differindex b47d666cf95d..e215b96f9301 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png Binary files differindex 03b0d2ac7138..a014e918441b 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png Binary files differindex 13d803cb98a5..bb8bec195185 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png Binary files differindex 3ae436b656d5..aa1737edf156 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png Binary files differindex 24824fc9d997..2c1434bbd09f 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png Binary files differindex af3819bbf044..dbdce3ef2f50 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png Binary files differindex 83dc251908ef..110186495da9 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png Binary files differindex 8d9d5923cfd0..e8e90697e55f 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png Binary files differindex 1310ec9eb3e4..8595158bed2c 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png Binary files differindex 1705074567c8..14844d403a26 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png Binary files differindex 7027b88ec157..1565a29b8f47 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png Binary files differindex 7027b88ec157..9b8fe879d55d 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_0.png b/core/res/res/drawable-hdpi/ic_notification_cast_0.png Binary files differnew file mode 100644 index 000000000000..74f7dc0c1f48 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_cast_0.png diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_1.png b/core/res/res/drawable-hdpi/ic_notification_cast_1.png Binary files differnew file mode 100644 index 000000000000..c6d267d8c021 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_cast_1.png diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_2.png b/core/res/res/drawable-hdpi/ic_notification_cast_2.png Binary files differnew file mode 100644 index 000000000000..699b299ffb8b --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_cast_2.png diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_on.png b/core/res/res/drawable-hdpi/ic_notification_cast_on.png Binary files differnew file mode 100644 index 000000000000..3eaf13a8520c --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_cast_on.png diff --git a/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png Binary files differdeleted file mode 100644 index 35f27df17260..000000000000 --- a/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/toast_frame.9.png b/core/res/res/drawable-hdpi/toast_frame.9.png Binary files differindex ca65994ad1fe..a804a8a94564 100644 --- a/core/res/res/drawable-hdpi/toast_frame.9.png +++ b/core/res/res/drawable-hdpi/toast_frame.9.png diff --git a/core/res/res/drawable-hdpi/toast_frame_holo.9.png b/core/res/res/drawable-hdpi/toast_frame_holo.9.png Binary files differdeleted file mode 100644 index a804a8a94564..000000000000 --- a/core/res/res/drawable-hdpi/toast_frame_holo.9.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/toast_frame.9.png b/core/res/res/drawable-ldpi/toast_frame.9.png Binary files differindex 3b344ff372eb..e64dc7575051 100644 --- a/core/res/res/drawable-ldpi/toast_frame.9.png +++ b/core/res/res/drawable-ldpi/toast_frame.9.png diff --git a/core/res/res/drawable-mdpi/ic_media_group_collapse.png b/core/res/res/drawable-mdpi/ic_media_group_collapse.png Binary files differdeleted file mode 100644 index 34454ac3e7e8..000000000000 --- a/core/res/res/drawable-mdpi/ic_media_group_collapse.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/ic_media_group_expand.png b/core/res/res/drawable-mdpi/ic_media_group_expand.png Binary files differdeleted file mode 100644 index 8ce5a448d813..000000000000 --- a/core/res/res/drawable-mdpi/ic_media_group_expand.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png Binary files differindex fa22d8244cf9..52e3a5a13236 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png Binary files differindex a686cd1cf2aa..319c57e8f343 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png Binary files differindex 6764598fee30..f98c0a85b53b 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png Binary files differindex 94e0bb641e91..b74cdb5b1a93 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png Binary files differindex 5ce2f205234d..a6a4bd012e53 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png Binary files differindex 5105e9081aeb..106fd3a97f94 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png Binary files differindex 68c06ed77aae..2c141ab8e692 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png Binary files differindex 6e9b14422993..0b62d0b509ff 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png Binary files differindex 45dc56f3d471..23442b06336b 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png Binary files differindex 46e743ad7ed6..42b329fad28e 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png Binary files differindex e384691fe8b8..58ff50685aca 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png Binary files differindex e384691fe8b8..25257f8b848d 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_0.png b/core/res/res/drawable-mdpi/ic_notification_cast_0.png Binary files differnew file mode 100644 index 000000000000..a51a3cbefd2f --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_cast_0.png diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_1.png b/core/res/res/drawable-mdpi/ic_notification_cast_1.png Binary files differnew file mode 100644 index 000000000000..e0813678a9f5 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_cast_1.png diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_2.png b/core/res/res/drawable-mdpi/ic_notification_cast_2.png Binary files differnew file mode 100644 index 000000000000..a7f4de43f203 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_cast_2.png diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_on.png b/core/res/res/drawable-mdpi/ic_notification_cast_on.png Binary files differnew file mode 100644 index 000000000000..42de8c4d5c10 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_cast_on.png diff --git a/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png Binary files differdeleted file mode 100644 index f9c8678f7f64..000000000000 --- a/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/toast_frame.9.png b/core/res/res/drawable-mdpi/toast_frame.9.png Binary files differindex 9e93fe71619f..778e4e67653d 100644 --- a/core/res/res/drawable-mdpi/toast_frame.9.png +++ b/core/res/res/drawable-mdpi/toast_frame.9.png diff --git a/core/res/res/drawable-mdpi/toast_frame_holo.9.png b/core/res/res/drawable-mdpi/toast_frame_holo.9.png Binary files differdeleted file mode 100644 index 778e4e67653d..000000000000 --- a/core/res/res/drawable-mdpi/toast_frame_holo.9.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/cab_background_top_holo_dark.9.png b/core/res/res/drawable-xhdpi/cab_background_top_holo_dark.9.png Binary files differindex 6b3157985ea5..7b6d48b6e41f 100644 --- a/core/res/res/drawable-xhdpi/cab_background_top_holo_dark.9.png +++ b/core/res/res/drawable-xhdpi/cab_background_top_holo_dark.9.png diff --git a/core/res/res/drawable-xhdpi/cab_background_top_holo_light.9.png b/core/res/res/drawable-xhdpi/cab_background_top_holo_light.9.png Binary files differindex df0121bb353f..bafe8781c2b8 100644 --- a/core/res/res/drawable-xhdpi/cab_background_top_holo_light.9.png +++ b/core/res/res/drawable-xhdpi/cab_background_top_holo_light.9.png diff --git a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png Binary files differdeleted file mode 100644 index 2fb7428f266f..000000000000 --- a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/ic_media_group_expand.png b/core/res/res/drawable-xhdpi/ic_media_group_expand.png Binary files differdeleted file mode 100644 index 5755b9da2f03..000000000000 --- a/core/res/res/drawable-xhdpi/ic_media_group_expand.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png Binary files differindex 1d48e12a3c68..4119cffb3932 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png Binary files differindex 2c8d1ec19b25..b629a5726b3b 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png Binary files differindex 00b20433ab79..fe81128d5443 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png Binary files differindex ce1d93954734..9b59eaf73976 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png Binary files differindex 3064b46e5299..1a513c1cd75e 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png Binary files differindex 431668642ce1..ff788032ea7b 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png Binary files differindex 25c4e3118cf0..4c4b624b9bdb 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png Binary files differindex 8e32bd2902ac..60f8c4dede13 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png Binary files differindex aeaa78ffaec3..cdb2f30f0bcf 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png Binary files differindex 85277fa12ebd..97a10a37a29f 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png Binary files differindex b01dbe8ceae4..a19a083706da 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png Binary files differindex c19a2ad0163b..db30613771ca 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_0.png b/core/res/res/drawable-xhdpi/ic_notification_cast_0.png Binary files differnew file mode 100644 index 000000000000..818c1cdd7a26 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_notification_cast_0.png diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_1.png b/core/res/res/drawable-xhdpi/ic_notification_cast_1.png Binary files differnew file mode 100644 index 000000000000..2a56e3131789 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_notification_cast_1.png diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_2.png b/core/res/res/drawable-xhdpi/ic_notification_cast_2.png Binary files differnew file mode 100644 index 000000000000..3515a76825e2 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_notification_cast_2.png diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_on.png b/core/res/res/drawable-xhdpi/ic_notification_cast_on.png Binary files differnew file mode 100644 index 000000000000..142065b4f575 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_notification_cast_on.png diff --git a/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png Binary files differdeleted file mode 100644 index 4cc0ee83faf8..000000000000 --- a/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/toast_frame.9.png b/core/res/res/drawable-xhdpi/toast_frame.9.png Binary files differindex 1f63420602c2..77e69c72ab9a 100644 --- a/core/res/res/drawable-xhdpi/toast_frame.9.png +++ b/core/res/res/drawable-xhdpi/toast_frame.9.png diff --git a/core/res/res/drawable-xhdpi/toast_frame_holo.9.png b/core/res/res/drawable-xhdpi/toast_frame_holo.9.png Binary files differdeleted file mode 100644 index 77e69c72ab9a..000000000000 --- a/core/res/res/drawable-xhdpi/toast_frame_holo.9.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/cab_background_top_holo_dark.9.png b/core/res/res/drawable-xxhdpi/cab_background_top_holo_dark.9.png Binary files differindex 418f322ce0f4..cbb4f4cf63da 100644 --- a/core/res/res/drawable-xxhdpi/cab_background_top_holo_dark.9.png +++ b/core/res/res/drawable-xxhdpi/cab_background_top_holo_dark.9.png diff --git a/core/res/res/drawable-xxhdpi/cab_background_top_holo_light.9.png b/core/res/res/drawable-xxhdpi/cab_background_top_holo_light.9.png Binary files differindex a5a59d4e61ba..6d467f7ee6d5 100644 --- a/core/res/res/drawable-xxhdpi/cab_background_top_holo_light.9.png +++ b/core/res/res/drawable-xxhdpi/cab_background_top_holo_light.9.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png Binary files differindex 7b0c383a5093..6fad4a641e81 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png Binary files differindex efb624e182a7..865617c0fd0e 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png Binary files differindex 5ee57e46a010..44d98d56d2b5 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png Binary files differindex 6bc2e4aa2265..b5b29b020900 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png Binary files differindex c13af9c80afd..c807b50ad076 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png Binary files differindex 744fb42a6633..3fc7188f31b4 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png Binary files differindex ca4d59c5b0c2..d54f44acb84b 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png Binary files differindex fde5688ad092..092fe8cc0dc5 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png Binary files differindex b8715c39d35b..17c1d9945b12 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png Binary files differindex 668bb2510a55..4fd580864841 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png Binary files differindex 7f54a623dd7e..906401e6f03f 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png Binary files differindex 2df924de1b82..d29e56300408 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png Binary files differnew file mode 100644 index 000000000000..7ef0d3d0e390 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png Binary files differnew file mode 100644 index 000000000000..ed04beb7e48b --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png Binary files differnew file mode 100644 index 000000000000..d62d27dd7a4c --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png Binary files differnew file mode 100644 index 000000000000..d5626024b0a5 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png diff --git a/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png Binary files differdeleted file mode 100644 index fea4774c8d72..000000000000 --- a/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/toast_frame.9.png b/core/res/res/drawable-xxhdpi/toast_frame.9.png Binary files differindex 882b9c61e020..edecb6320de5 100644 --- a/core/res/res/drawable-xxhdpi/toast_frame.9.png +++ b/core/res/res/drawable-xxhdpi/toast_frame.9.png diff --git a/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png b/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png Binary files differdeleted file mode 100644 index edecb6320de5..000000000000 --- a/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png +++ /dev/null diff --git a/core/res/res/drawable/ic_notification_cast_connecting.xml b/core/res/res/drawable/ic_notification_cast_connecting.xml new file mode 100644 index 000000000000..a390bce58943 --- /dev/null +++ b/core/res/res/drawable/ic_notification_cast_connecting.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright 2013, 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. + */ +--> +<animation-list + xmlns:android="http://schemas.android.com/apk/res/android" + android:oneshot="false"> + <item android:drawable="@drawable/ic_notification_cast_0" android:duration="500" /> + <item android:drawable="@drawable/ic_notification_cast_1" android:duration="500" /> + <item android:drawable="@drawable/ic_notification_cast_2" android:duration="500" /> + <item android:drawable="@drawable/ic_notification_cast_1" android:duration="500" /> +</animation-list> diff --git a/core/res/res/layout/immersive_mode_cling.xml b/core/res/res/layout/immersive_mode_cling.xml index f97225eaeb9e..c0cd93db456b 100644 --- a/core/res/res/layout/immersive_mode_cling.xml +++ b/core/res/res/layout/immersive_mode_cling.xml @@ -37,8 +37,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/cling_bg" - android:paddingLeft="14dp" - android:paddingRight="14dp" + android:paddingStart="14dp" + android:paddingEnd="14dp" android:paddingTop="24dp" android:paddingBottom="24dp"> <TextView diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml new file mode 100644 index 000000000000..d1c6267ea4e7 --- /dev/null +++ b/core/res/res/layout/media_route_chooser_dialog.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:divider="?android:attr/dividerHorizontal" + android:showDividers="middle"> + <!-- List of routes. --> + <ListView android:id="@+id/media_route_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <!-- Content to show when list is empty. --> + <LinearLayout android:id="@android:id/empty" + android:layout_width="match_parent" + android:layout_height="64dp" + android:orientation="horizontal" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:visibility="gone"> + <ProgressBar android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:paddingLeft="16dp" + android:text="@string/media_route_chooser_searching" /> + </LinearLayout> + + <!-- Settings button. --> + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?attr/buttonBarStyle"> + <Button android:id="@+id/media_route_extended_settings_button" + android:layout_width="match_parent" + android:layout_height="match_parent" + style="?attr/buttonBarButtonStyle" + android:gravity="center" + android:text="@string/media_route_chooser_extended_settings" + android:visibility="gone" /> + </LinearLayout> +</LinearLayout> diff --git a/core/res/res/layout/media_route_chooser_layout.xml b/core/res/res/layout/media_route_chooser_layout.xml deleted file mode 100644 index 5fcb8c8178bb..000000000000 --- a/core/res/res/layout/media_route_chooser_layout.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 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. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:showDividers="middle" - android:divider="?android:attr/dividerHorizontal"> - <LinearLayout android:layout_width="match_parent" - android:layout_height="?android:attr/listPreferredItemHeight" - android:gravity="center_vertical" - android:padding="8dp"> - <ImageView android:id="@+id/volume_icon" - android:layout_width="48dp" - android:layout_height="48dp" - android:src="@android:drawable/ic_audio_vol" - android:gravity="center" - android:scaleType="center" /> - <SeekBar android:id="@+id/volume_slider" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginStart="8dp" - android:layout_marginEnd="8dp" /> - <ImageButton android:id="@+id/extended_settings" - android:layout_width="48dp" - android:layout_height="48dp" - android:background="?android:attr/selectableItemBackground" - android:src="@android:drawable/ic_sysbar_quicksettings" - android:visibility="gone" /> - </LinearLayout> - <ListView android:id="@id/list" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> -</LinearLayout> diff --git a/core/res/res/layout/media_route_controller_dialog.xml b/core/res/res/layout/media_route_controller_dialog.xml new file mode 100644 index 000000000000..78287e0ba585 --- /dev/null +++ b/core/res/res/layout/media_route_controller_dialog.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:divider="?android:attr/dividerHorizontal" + android:showDividers="middle"> + <!-- Optional volume slider section. --> + <LinearLayout android:id="@+id/media_route_volume_layout" + android:layout_width="match_parent" + android:layout_height="64dp" + android:gravity="center_vertical" + android:padding="8dp" + android:visibility="gone"> + <ImageView android:layout_width="48dp" + android:layout_height="48dp" + android:src="@drawable/ic_audio_vol" + android:gravity="center" + android:scaleType="center" /> + <SeekBar android:id="@+id/media_route_volume_slider" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" /> + </LinearLayout> + + <!-- Optional content view section. --> + <FrameLayout android:id="@+id/media_route_control_frame" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" /> + + <!-- Disconnect button. --> + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?attr/buttonBarStyle"> + <Button android:id="@+id/media_route_disconnect_button" + android:layout_width="match_parent" + android:layout_height="match_parent" + style="?attr/buttonBarButtonStyle" + android:gravity="center" + android:text="@string/media_route_controller_disconnect" /> + </LinearLayout> +</LinearLayout> diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml index 423d54453737..bdca433c1c58 100644 --- a/core/res/res/layout/media_route_list_item.xml +++ b/core/res/res/layout/media_route_list_item.xml @@ -20,13 +20,6 @@ android:background="@drawable/item_background_activated_holo_dark" android:gravity="center_vertical"> - <ImageView android:layout_width="56dp" - android:layout_height="56dp" - android:scaleType="center" - android:id="@+id/icon" - android:visibility="gone" - android:duplicateParentState="true" /> - <LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" @@ -53,14 +46,4 @@ android:duplicateParentState="true" /> </LinearLayout> - <ImageButton - android:layout_width="56dp" - android:layout_height="56dp" - android:id="@+id/expand_button" - android:background="?android:attr/selectableItemBackground" - android:src="@drawable/ic_media_group_expand" - android:scaleType="center" - android:visibility="gone" - android:duplicateParentState="true" /> - </LinearLayout> diff --git a/core/res/res/layout/media_route_list_item_checkable.xml b/core/res/res/layout/media_route_list_item_checkable.xml deleted file mode 100644 index 5fb82f71d153..000000000000 --- a/core/res/res/layout/media_route_list_item_checkable.xml +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 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. ---> - -<com.android.internal.view.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="?android:attr/listPreferredItemHeight" - android:background="?android:attr/selectableItemBackground" - android:gravity="center_vertical"> - - <ImageView android:layout_width="56dp" - android:layout_height="56dp" - android:scaleType="center" - android:id="@+id/icon" - android:visibility="gone" /> - - <LinearLayout android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:orientation="vertical" - android:gravity="start|center_vertical" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> - - <TextView android:id="@android:id/text1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceMedium" /> - - <TextView android:id="@android:id/text2" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceSmall" /> - </LinearLayout> - - <CheckBox - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="16dp" - android:id="@+id/check" - android:focusable="false" - android:clickable="false" /> - -</com.android.internal.view.CheckableLinearLayout> diff --git a/core/res/res/layout/media_route_list_item_collapse_group.xml b/core/res/res/layout/media_route_list_item_collapse_group.xml deleted file mode 100644 index 323e24db4c30..000000000000 --- a/core/res/res/layout/media_route_list_item_collapse_group.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 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. ---> - -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?android:attr/selectableItemBackground"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="?android:attr/listPreferredItemHeightSmall" - android:background="#19ffffff" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:gravity="center_vertical"> - - <TextView android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:singleLine="true" - android:ellipsize="marquee" - android:text="@string/media_route_chooser_grouping_done" - android:textAppearance="?android:attr/textAppearanceMedium" /> - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/ic_media_group_collapse" - android:scaleType="center" /> - - </LinearLayout> -</FrameLayout>
\ No newline at end of file diff --git a/core/res/res/layout/media_route_list_item_section_header.xml b/core/res/res/layout/media_route_list_item_section_header.xml deleted file mode 100644 index 949635ff8355..000000000000 --- a/core/res/res/layout/media_route_list_item_section_header.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 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. ---> - -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="16dp"> - <TextView - android:id="@android:id/text1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceSmall" - android:background="#19ffffff" - android:textStyle="bold" - android:textAllCaps="true" - android:gravity="center_vertical" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:minHeight="24dp" - /> -</FrameLayout> diff --git a/core/res/res/values-mcc310-mnc260/config.xml b/core/res/res/values-mcc310-mnc260/config.xml index 886ecbe5389c..d602c9fa634c 100644 --- a/core/res/res/values-mcc310-mnc260/config.xml +++ b/core/res/res/values-mcc310-mnc260/config.xml @@ -21,22 +21,6 @@ for different hardware and product builds. --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering --> - <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or - <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH --> - <integer-array translatable="false" name="config_tether_upstream_types"> - <item>1</item> - <item>4</item> - <item>7</item> - <item>9</item> - </integer-array> - - <!-- String containing the apn value for tethering. May be overriden by secure settings - TETHER_DUN_APN. Value is a comma separated series of strings: - "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type" - note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" --> - <string translatable="false" name="config_tether_apndata">T-Mobile Tethering,pcweb.tmobile.com,,,,,,,,,310,260,,DUN</string> - <!-- Configure mobile network MTU. Carrier specific value is set here. --> <integer name="config_mobile_mtu">1440</integer> diff --git a/core/res/res/values-mcc311-mnc190/config.xml b/core/res/res/values-mcc311-mnc190/config.xml new file mode 100644 index 000000000000..a6c4d1b6dfe2 --- /dev/null +++ b/core/res/res/values-mcc311-mnc190/config.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, 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 my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering --> + <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or + <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH --> + <integer-array translatable="false" name="config_tether_upstream_types"> + <item>1</item> + <item>4</item> + <item>7</item> + <item>9</item> + </integer-array> + + <!-- String containing the apn value for tethering. May be overriden by secure settings + TETHER_DUN_APN. Value is a comma separated series of strings: + "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type" + note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" --> + <string translatable="false" name="config_tether_apndata">Tether,broadband.cellular1.net,,,,,,,,,311,190,,DUN</string> + +</resources> diff --git a/core/res/res/values-mcc404-mnc85/config.xml b/core/res/res/values-mcc404/config.xml index fd780abbf7dd..a106b0ae2cec 100644 --- a/core/res/res/values-mcc404-mnc85/config.xml +++ b/core/res/res/values-mcc404/config.xml @@ -20,6 +20,7 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Show roaming icon though same named operators. --> <string-array translatable="false" name="config_sameNamedOperatorConsideredRoaming"> - <item>40483</item> + <item>404</item> + <item>405</item> </string-array> </resources> diff --git a/core/res/res/values-mcc302-mnc500/config.xml b/core/res/res/values-mcc405/config.xml index 706570c0bd6b..a106b0ae2cec 100644 --- a/core/res/res/values-mcc302-mnc500/config.xml +++ b/core/res/res/values-mcc405/config.xml @@ -18,8 +18,9 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Don't use roaming icon for considered operators --> - <string-array translatable="false" name="config_operatorConsideredNonRoaming"> - <item>302</item> + <!-- Show roaming icon though same named operators. --> + <string-array translatable="false" name="config_sameNamedOperatorConsideredRoaming"> + <item>404</item> + <item>405</item> </string-array> </resources> diff --git a/core/res/res/values-mcc404-mnc17/config.xml b/core/res/res/values-mcc520/config.xml index 685d0121931d..b2f3efa891ac 100644 --- a/core/res/res/values-mcc404-mnc17/config.xml +++ b/core/res/res/values-mcc520/config.xml @@ -20,6 +20,6 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Show roaming icon though same named operators. --> <string-array translatable="false" name="config_sameNamedOperatorConsideredRoaming"> - <item>40491</item> + <item>520</item> </string-array> </resources> diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index ef30b98a60f1..91af50aab8fb 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -22,7 +22,7 @@ <!-- Do not translate. These are all of the drawable resources that should be preloaded by the zygote process before it starts forking application processes. --> <array name="preloaded_drawables"> - <item>@drawable/toast_frame_holo</item> + <item>@drawable/toast_frame</item> <item>@drawable/btn_check_on_pressed_holo_light</item> <item>@drawable/btn_check_on_pressed_holo_dark</item> <item>@drawable/btn_check_on_holo_light</item> diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml index 10a5d851fac3..18e4f2f9b3a8 100644 --- a/core/res/res/values/bools.xml +++ b/core/res/res/values/bools.xml @@ -26,5 +26,4 @@ <bool name="show_ongoing_ime_switcher">true</bool> <bool name="action_bar_expanded_action_views_exclusive">true</bool> <bool name="target_honeycomb_needs_options_menu">true</bool> - <bool name="flip_controller_fallback_keys">false</bool> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 42ea384bcdbb..a3b81327b738 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -352,6 +352,10 @@ <!-- Wifi driver supports batched scan --> <bool translatable="false" name="config_wifi_batched_scan_supported">false</bool> + <!-- Wifi driver's fallback country code; WS is ISO-Alpha2 code for Samoa which + has restrictions on can be scanned; which may satisfy quite a few regulatory issues. --> + <string translatable="false" name="config_wifi_unknown_country_code">WS</string> + <!-- Flag indicating whether the we should enable the automatic brightness in Settings. Software implementation will be used if config_hardware_auto_brightness_available is not set --> <bool name="config_automatic_brightness_available">false</bool> @@ -585,8 +589,8 @@ <!-- Disable lockscreen rotation by default --> <bool name="config_enableLockScreenRotation">false</bool> - <!-- Disable lockscreen translucent decor by default --> - <bool name="config_enableLockScreenTranslucentDecor">false</bool> + <!-- Enable lockscreen translucent decor by default --> + <bool name="config_enableLockScreenTranslucentDecor">true</bool> <!-- Enable translucent decor by default --> <bool name="config_enableTranslucentDecor">true</bool> @@ -1176,6 +1180,22 @@ where if the preferred is used we don't try the others. --> <bool name="config_dontPreferApn">false</bool> + <!-- The list of ril radio technologies (see ServiceState.java) which only support + a single data connection at one time. This may change by carrier via + overlays (some don't support multiple pdp on UMTS). All unlisted radio + tech types support unlimited types (practically only 2-4 used). --> + <integer-array name="config_onlySingleDcAllowed"> + <item>4</item> <!-- IS95A --> + <item>5</item> <!-- IS95B --> + <item>6</item> <!-- 1xRTT --> + <item>7</item> <!-- EVDO_0 --> + <item>8</item> <!-- EVDO_A --> + <item>12</item> <!-- EVDO_B --> + </integer-array> + + <!-- Set to true if after a provisioning apn the radio should be restarted --> + <bool name="config_restartRadioAfterProvisioning">false</bool> + <!-- Vibrator pattern to be used as the default for notifications that specify DEFAULT_VIBRATE. --> @@ -1288,4 +1308,12 @@ <item>333</item> <item>353</item> </string-array> + + <!-- Override the default detection behavior for the framework method + android.view.ViewConfiguration#hasPermanentMenuKey(). + Valid settings are: + 0 - No change. Use the default autodetection behavior. + 1 - The device DOES have a permanent menu key; ignore autodetection. + 2 - The device DOES NOT have a permanent menu key; ignore autodetection. --> + <integer name="config_overrideHasPermanentMenuKey">0</integer> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 62f26c632303..098fbbe0c3c7 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -625,9 +625,9 @@ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_processOutgoingCalls">reroute outgoing calls</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_processOutgoingCalls">Allows the app to process - outgoing calls and change the number to be dialed. This permission allows - the app to monitor, redirect, or prevent outgoing calls.</string> + <string name="permdesc_processOutgoingCalls">Allows the app to see the + number being dialed during an outgoing call with the option to redirect + the call to a different number or abort the call altogether.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_receiveSms">receive text messages (SMS)</string> @@ -932,13 +932,13 @@ <!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_getAppOpsStats">Allows the app to retrieve collected application operation statistics. Not for use by normal apps.</string> - + <!-- [CHAR LIMIT=NONE] Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_updateAppOpsStats">modify app ops statistics</string> <!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_updateAppOpsStats">Allows the app to modify collected application operation statistics. Not for use by normal apps.</string> - + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_backup">control system backup and restore</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> @@ -1052,6 +1052,12 @@ interface of a wallpaper. Should never be needed for normal apps.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindRemoteDisplay">bind to a remote display</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindRemoteDisplay">Allows the holder to bind to the top-level + interface of a remote display. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_bindRemoteViews">bind to a widget service</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_bindRemoteViews">Allows the holder to bind to the top-level @@ -2241,6 +2247,10 @@ <!-- Other SIP address type. Same context as Other phone type. --> <string name="sipAddressTypeOther">Other</string> + <!-- Error message that is displayed when the user clicks on a quick contacts badge, but + there is no contacts application installed that can display the quick contact --> + <string name="quick_contacts_not_available">No application found to view this contact.</string> + <!-- Instructions telling the user to enter their SIM PIN to unlock the keyguard. Displayed in one line in a large font. --> <string name="keyguard_password_enter_pin_code">Type PIN code</string> @@ -4090,12 +4100,24 @@ <!-- Description of a wireless display route. [CHAR LIMIT=50] --> <string name="wireless_display_route_description">Wireless display</string> - <!-- "Done" button for MediaRouter chooser dialog when grouping routes. [CHAR LIMIT=NONE] --> - <string name="media_route_chooser_grouping_done">Done</string> - <!-- Content description of a MediaRouteButton for accessibility support --> <string name="media_route_button_content_description">Media output</string> + <!-- Title of the media route chooser dialog. [CHAR LIMIT=40] --> + <string name="media_route_chooser_title">Connect to device</string> + + <!-- Title of the media route chooser dialog for selecting remote display routes. [CHAR LIMIT=40] --> + <string name="media_route_chooser_title_for_remote_display">Cast screen to device</string> + + <!-- Placeholder text to show when no devices have been found. [CHAR LIMIT=50] --> + <string name="media_route_chooser_searching">Searching for devices\u2026</string> + + <!-- Button to access extended settings. [CHAR LIMIT=30] --> + <string name="media_route_chooser_extended_settings">Settings</string> + + <!-- Button to disconnect from a media route. [CHAR LIMIT=30] --> + <string name="media_route_controller_disconnect">Disconnect</string> + <!-- Status message for remote routes attempting to scan/determine availability --> <string name="media_route_status_scanning">Scanning...</string> @@ -4128,10 +4150,14 @@ <!-- Title text to append when the display is secure. [CHAR LIMIT=30] --> <string name="display_manager_overlay_display_secure_suffix">, secure</string> + <!-- Title of the notification to indicate the process of connecting to a wifi display. [CHAR LIMIT=50] --> + <string name="wifi_display_notification_connecting_title">Casting screen</string> + <!-- Message of the notification to indicate the process of connecting to a wifi display. [CHAR LIMIT=80] --> + <string name="wifi_display_notification_connecting_message">Connecting to <xliff:g id="name">%1$s</xliff:g></string> <!-- Title of the notification to indicate an active wifi display connection. [CHAR LIMIT=50] --> - <string name="wifi_display_notification_title">Wireless display is connected</string> + <string name="wifi_display_notification_connected_title">Casting screen</string> <!-- Message of the notification to indicate an active wifi display connection. [CHAR LIMIT=80] --> - <string name="wifi_display_notification_message">This screen is showing on another device</string> + <string name="wifi_display_notification_connected_message">Connected to <xliff:g id="name">%1$s</xliff:g></string> <!-- Label of a button to disconnect an active wifi display connection. [CHAR LIMIT=25] --> <string name="wifi_display_notification_disconnect">Disconnect</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 22a9402a97a2..120027629a64 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -283,11 +283,11 @@ <java-symbol type="bool" name="config_safe_media_volume_enabled" /> <java-symbol type="bool" name="config_camera_sound_forced" /> <java-symbol type="bool" name="config_dontPreferApn" /> + <java-symbol type="bool" name="config_restartRadioAfterProvisioning" /> <java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" /> <java-symbol type="bool" name="config_useFixedVolume" /> <java-symbol type="bool" name="config_forceDefaultOrientation" /> <java-symbol type="bool" name="config_wifi_batched_scan_supported" /> - <java-symbol type="bool" name="flip_controller_fallback_keys" /> <java-symbol type="integer" name="config_cursorWindowSize" /> <java-symbol type="integer" name="config_extraFreeKbytesAdjust" /> @@ -318,6 +318,7 @@ <java-symbol type="integer" name="config_mobile_mtu" /> <java-symbol type="integer" name="config_volte_replacement_rat"/> <java-symbol type="integer" name="config_valid_wappush_index" /> + <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" /> <java-symbol type="color" name="tab_indicator_text_v4" /> @@ -477,6 +478,7 @@ <java-symbol type="string" name="config_ntpServer" /> <java-symbol type="string" name="config_tether_apndata" /> <java-symbol type="string" name="config_useragentprofile_url" /> + <java-symbol type="string" name="config_wifi_unknown_country_code" /> <java-symbol type="string" name="config_wifi_p2p_device_type" /> <java-symbol type="string" name="contentServiceSync" /> <java-symbol type="string" name="contentServiceSyncNotificationTitle" /> @@ -689,6 +691,7 @@ <java-symbol type="string" name="mobile_provisioning_apn" /> <java-symbol type="string" name="mobile_provisioning_url" /> <java-symbol type="string" name="mobile_redirected_provisioning_url" /> + <java-symbol type="string" name="quick_contacts_not_available" /> <java-symbol type="string" name="reboot_safemode_confirm" /> <java-symbol type="string" name="reboot_safemode_title" /> <java-symbol type="string" name="relationTypeAssistant" /> @@ -1096,7 +1099,11 @@ <java-symbol type="drawable" name="notification_template_icon_bg" /> <java-symbol type="drawable" name="notification_template_icon_low_bg" /> <java-symbol type="drawable" name="ic_media_route_on_holo_dark" /> + <java-symbol type="drawable" name="ic_media_route_off_holo_dark" /> + <java-symbol type="drawable" name="ic_media_route_connecting_holo_dark" /> <java-symbol type="drawable" name="ic_media_route_disabled_holo_dark" /> + <java-symbol type="drawable" name="ic_notification_cast_connecting" /> + <java-symbol type="drawable" name="ic_notification_cast_on" /> <java-symbol type="drawable" name="cling_button" /> <java-symbol type="drawable" name="cling_arrow_up" /> <java-symbol type="drawable" name="cling_bg" /> @@ -1253,17 +1260,17 @@ <java-symbol type="attr" name="mediaRouteButtonStyle" /> <java-symbol type="attr" name="externalRouteEnabledDrawable" /> - <java-symbol type="id" name="extended_settings" /> - <java-symbol type="id" name="check" /> - <java-symbol type="id" name="volume_slider" /> - <java-symbol type="id" name="volume_icon" /> - <java-symbol type="drawable" name="ic_media_route_on_holo_dark" /> - <java-symbol type="layout" name="media_route_chooser_layout" /> - <java-symbol type="layout" name="media_route_list_item_top_header" /> - <java-symbol type="layout" name="media_route_list_item_section_header" /> + <java-symbol type="layout" name="media_route_chooser_dialog" /> + <java-symbol type="layout" name="media_route_controller_dialog" /> <java-symbol type="layout" name="media_route_list_item" /> - <java-symbol type="layout" name="media_route_list_item_checkable" /> - <java-symbol type="layout" name="media_route_list_item_collapse_group" /> + <java-symbol type="id" name="media_route_list" /> + <java-symbol type="id" name="media_route_volume_layout" /> + <java-symbol type="id" name="media_route_volume_slider" /> + <java-symbol type="id" name="media_route_control_frame" /> + <java-symbol type="id" name="media_route_disconnect_button" /> + <java-symbol type="id" name="media_route_extended_settings_button" /> + <java-symbol type="string" name="media_route_chooser_title" /> + <java-symbol type="string" name="media_route_chooser_title_for_remote_display" /> <java-symbol type="string" name="bluetooth_a2dp_audio_route_name" /> <java-symbol type="dimen" name="config_minScalingSpan" /> @@ -1435,6 +1442,7 @@ <java-symbol type="array" name="config_locationProviderPackageNames" /> <java-symbol type="array" name="config_defaultNotificationVibePattern" /> <java-symbol type="array" name="config_notificationFallbackVibePattern" /> + <java-symbol type="array" name="config_onlySingleDcAllowed" /> <java-symbol type="bool" name="config_animateScreenLights" /> <java-symbol type="bool" name="config_automatic_brightness_available" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> @@ -1450,7 +1458,6 @@ <java-symbol type="color" name="config_defaultNotificationColor" /> <java-symbol type="color" name="input_method_navigation_guard" /> <java-symbol type="drawable" name="ic_notification_ime_default" /> - <java-symbol type="drawable" name="ic_notify_wifidisplay" /> <java-symbol type="drawable" name="ic_menu_refresh" /> <java-symbol type="drawable" name="stat_notify_car_mode" /> <java-symbol type="drawable" name="stat_notify_disabled" /> @@ -1593,8 +1600,10 @@ <java-symbol type="string" name="vpn_lockdown_error" /> <java-symbol type="string" name="vpn_lockdown_config" /> <java-symbol type="string" name="wallpaper_binding_label" /> - <java-symbol type="string" name="wifi_display_notification_title" /> - <java-symbol type="string" name="wifi_display_notification_message" /> + <java-symbol type="string" name="wifi_display_notification_connecting_title" /> + <java-symbol type="string" name="wifi_display_notification_connecting_message" /> + <java-symbol type="string" name="wifi_display_notification_connected_title" /> + <java-symbol type="string" name="wifi_display_notification_connected_message" /> <java-symbol type="string" name="wifi_display_notification_disconnect" /> <java-symbol type="style" name="Theme.Dialog.AppError" /> <java-symbol type="style" name="Theme.Toast" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 4c80e7d3d85d..c8d9fc66af58 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -1048,7 +1048,7 @@ please see themes_device_defaults.xml. <item name="presentationTheme">@android:style/Theme.Holo.Dialog.Presentation</item> <!-- Toast attributes --> - <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item> + <item name="toastFrameBackground">@android:drawable/toast_frame</item> <!-- Panel attributes --> <item name="panelBackground">@android:drawable/menu_hardkey_panel_holo_dark</item> @@ -1363,7 +1363,7 @@ please see themes_device_defaults.xml. <item name="presentationTheme">@android:style/Theme.Holo.Light.Dialog.Presentation</item> <!-- Toast attributes --> - <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item> + <item name="toastFrameBackground">@android:drawable/toast_frame</item> <!-- Panel attributes --> <item name="panelBackground">@android:drawable/menu_hardkey_panel_holo_light</item> diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml index 7af3b9c0aae2..3215e174b73e 100644 --- a/core/res/res/xml/power_profile.xml +++ b/core/res/res/xml/power_profile.xml @@ -58,4 +58,12 @@ </array> <!-- This is the battery capacity in mAh (measured at nominal voltage) --> <item name="battery.capacity">1000</item> + + <array name="wifi.batchedscan"> <!-- mA --> + <value>.0002</value> <!-- 1-8/hr --> + <value>.002</value> <!-- 9-64/hr --> + <value>.02</value> <!-- 65-512/hr --> + <value>.2</value> <!-- 513-4,096/hr --> + <value>2</value> <!-- 4097-/hr --> + </array> </device> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java index 5a4a2d0ba8d3..9d97ac58011e 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java @@ -101,9 +101,10 @@ public class NetworkState { } /* - * Transition from CONNECTED -> DISCONNECTED: - * CONNECTED->DISCONNECTING->DISCONNECTED - * return false if any state transition is not valid and save a message in mReason + * Verifies state transition from CONNECTED->...-> DISCONNECTED. + * + * returns false if initial state or target state is not correct, or if there is + * any transition from DISCONNECTING/DISCONNECTED -> CONNECTED. */ public boolean transitToDisconnection () { mReason = "states: " + printStates(); @@ -120,13 +121,13 @@ public class NetworkState { for (int i = 1; i < mStateDepository.size() - 1; i++) { State preState = mStateDepository.get(i-1); State curState = mStateDepository.get(i); - if ((preState == State.CONNECTED) && ((curState == State.DISCONNECTING) || + if (preState == curState) { + continue; + } else if ((preState == State.CONNECTED) && ((curState == State.DISCONNECTING) || (curState == State.DISCONNECTED))) { continue; } else if ((preState == State.DISCONNECTING) && (curState == State.DISCONNECTED)) { continue; - } else if ((preState == State.DISCONNECTED) && (curState == State.DISCONNECTED)) { - continue; } else { mReason += " Transition state from " + preState.toString() + " to " + curState.toString() + " is not valid."; @@ -136,7 +137,12 @@ public class NetworkState { return true; } - // DISCONNECTED->CONNECTING->CONNECTED + /* + * Verifies state transition from DISCONNECTED->...-> CONNECTED. + * + * returns false if initial state or target state is not correct, or if there is + * any transition from CONNECED -> DISCONNECTED. + */ public boolean transitToConnection() { mReason = "states: " + printStates(); if (mStateDepository.get(0) != State.DISCONNECTED) { @@ -152,14 +158,15 @@ public class NetworkState { for (int i = 1; i < mStateDepository.size(); i++) { State preState = mStateDepository.get(i-1); State curState = mStateDepository.get(i); - if ((preState == State.DISCONNECTED) && ((curState == State.CONNECTING) || - (curState == State.CONNECTED) || (curState == State.DISCONNECTED))) { + if (preState == curState) { continue; - } else if ((preState == State.CONNECTING) && (curState == State.CONNECTED)) { - continue; - } else if ((preState == State.CONNECTED) && (curState == State.CONNECTED)) { + } + if ((preState == State.DISCONNECTED) && ((curState == State.CONNECTING) || + (curState == State.CONNECTED))) { continue; - } else { + } else if ((preState == State.CONNECTING) && (curState == State.CONNECTED)) { + continue; + } else { mReason += " Transition state from " + preState.toString() + " to " + curState.toString() + " is not valid."; return false; diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java index 76b702ecc87b..4a58f885ff08 100644 --- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java +++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java @@ -36,8 +36,6 @@ import com.android.bandwidthtest.util.BandwidthTestUtil; import com.android.bandwidthtest.util.ConnectionUtil; import java.io.File; -import java.util.HashMap; -import java.util.Map; /** * Test that downloads files from a test server and reports the bandwidth metrics collected. @@ -131,8 +129,8 @@ public class BandwidthTest extends InstrumentationTestCase { results.putString("device_id", mDeviceId); results.putString("timestamp", ts); results.putInt("size", FILE_SIZE); - AddStatsToResults(PROF_LABEL, prof_stats, results); - AddStatsToResults(PROC_LABEL, proc_stats, results); + addStatsToResults(PROF_LABEL, prof_stats, results, mUid); + addStatsToResults(PROC_LABEL, proc_stats, results, mUid); getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); // Clean up. @@ -185,8 +183,8 @@ public class BandwidthTest extends InstrumentationTestCase { results.putString("device_id", mDeviceId); results.putString("timestamp", ts); results.putInt("size", FILE_SIZE); - AddStatsToResults(PROF_LABEL, prof_stats, results); - AddStatsToResults(PROC_LABEL, proc_stats, results); + addStatsToResults(PROF_LABEL, prof_stats, results, mUid); + addStatsToResults(PROC_LABEL, proc_stats, results, mUid); getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); // Clean up. @@ -242,8 +240,9 @@ public class BandwidthTest extends InstrumentationTestCase { results.putString("device_id", mDeviceId); results.putString("timestamp", ts); results.putInt("size", FILE_SIZE); - AddStatsToResults(PROF_LABEL, prof_stats, results); - AddStatsToResults(PROC_LABEL, proc_stats, results); + addStatsToResults(PROF_LABEL, prof_stats, results, mUid); + // remember to use download manager uid for proc stats + addStatsToResults(PROC_LABEL, proc_stats, results, downloadManagerUid); getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); // Clean up. @@ -302,46 +301,35 @@ public class BandwidthTest extends InstrumentationTestCase { * @param label to attach to this given stats. * @param stats {@link NetworkStats} to add. * @param results {@link Bundle} to be added to. + * @param uid for which to report the results. */ - public void AddStatsToResults(String label, NetworkStats stats, Bundle results){ + public void addStatsToResults(String label, NetworkStats stats, Bundle results, int uid){ if (results == null || results.isEmpty()) { Log.e(LOG_TAG, "Empty bundle provided."); return; } - // Merge stats across all sets. - Map<Integer, Entry> totalStats = new HashMap<Integer, Entry>(); + Entry totalStats = null; for (int i = 0; i < stats.size(); ++i) { Entry statsEntry = stats.getValues(i, null); // We are only interested in the all inclusive stats. if (statsEntry.tag != 0) { continue; } - Entry mapEntry = null; - if (totalStats.containsKey(statsEntry.uid)) { - mapEntry = totalStats.get(statsEntry.uid); - switch (statsEntry.set) { - case NetworkStats.SET_ALL: - mapEntry.rxBytes = statsEntry.rxBytes; - mapEntry.txBytes = statsEntry.txBytes; - break; - case NetworkStats.SET_DEFAULT: - case NetworkStats.SET_FOREGROUND: - mapEntry.rxBytes += statsEntry.rxBytes; - mapEntry.txBytes += statsEntry.txBytes; - break; - default: - Log.w(LOG_TAG, "Invalid state found in NetworkStats."); - } + // skip stats for other uids + if (statsEntry.uid != uid) { + continue; + } + if (totalStats == null || statsEntry.set == NetworkStats.SET_ALL) { + totalStats = statsEntry; } else { - totalStats.put(statsEntry.uid, statsEntry); + totalStats.rxBytes += statsEntry.rxBytes; + totalStats.txBytes += statsEntry.txBytes; } } - // Ouput merged stats to bundle. - for (Entry entry : totalStats.values()) { - results.putInt(label + "uid", entry.uid); - results.putLong(label + "tx", entry.txBytes); - results.putLong(label + "rx", entry.rxBytes); - } + // Output merged stats to bundle. + results.putInt(label + "uid", totalStats.uid); + results.putLong(label + "tx", totalStats.txBytes); + results.putLong(label + "rx", totalStats.rxBytes); } /** diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm index 695a74f83810..01d22ee69178 100644 --- a/data/keyboards/Generic.kcm +++ b/data/keyboards/Generic.kcm @@ -477,4 +477,128 @@ key ESCAPE { ctrl: fallback MENU } -### Gamepad buttons are handled by the view root ### +### Gamepad buttons ### + +key BUTTON_A { + base: fallback DPAD_CENTER +} + +key BUTTON_B { + base: fallback BACK +} + +key BUTTON_C { + base: fallback DPAD_CENTER +} + +key BUTTON_X { + base: fallback DPAD_CENTER +} + +key BUTTON_Y { + base: fallback BACK +} + +key BUTTON_Z { + base: fallback DPAD_CENTER +} + +key BUTTON_L1 { + base: none +} + +key BUTTON_R1 { + base: none +} + +key BUTTON_L2 { + base: none +} + +key BUTTON_R2 { + base: none +} + +key BUTTON_THUMBL { + base: fallback DPAD_CENTER +} + +key BUTTON_THUMBR { + base: fallback DPAD_CENTER +} + +key BUTTON_START { + base: fallback DPAD_CENTER +} + +key BUTTON_SELECT { + base: fallback MENU +} + +key BUTTON_MODE { + base: fallback MENU +} + +key BUTTON_1 { + base: fallback DPAD_CENTER +} + +key BUTTON_2 { + base: fallback DPAD_CENTER +} + +key BUTTON_3 { + base: fallback DPAD_CENTER +} + +key BUTTON_4 { + base: fallback DPAD_CENTER +} + +key BUTTON_5 { + base: fallback DPAD_CENTER +} + +key BUTTON_6 { + base: fallback DPAD_CENTER +} + +key BUTTON_7 { + base: fallback DPAD_CENTER +} + +key BUTTON_8 { + base: fallback DPAD_CENTER +} + +key BUTTON_9 { + base: fallback DPAD_CENTER +} + +key BUTTON_10 { + base: fallback DPAD_CENTER +} + +key BUTTON_11 { + base: fallback DPAD_CENTER +} + +key BUTTON_12 { + base: fallback DPAD_CENTER +} + +key BUTTON_13 { + base: fallback DPAD_CENTER +} + +key BUTTON_14 { + base: fallback DPAD_CENTER +} + +key BUTTON_15 { + base: fallback DPAD_CENTER +} + +key BUTTON_16 { + base: fallback DPAD_CENTER +} diff --git a/data/sounds/AudioPackage10.mk b/data/sounds/AudioPackage10.mk index 783e1f871211..ae4bc8839aa0 100644 --- a/data/sounds/AudioPackage10.mk +++ b/data/sounds/AudioPackage10.mk @@ -17,19 +17,19 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \ $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \ $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \ $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \ $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \ $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \ $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \ - $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \ - $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \ + $(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:system/media/audio/ui/Lock.ogg \ + $(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:system/media/audio/ui/Unlock.ogg \ $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \ $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \ diff --git a/data/sounds/AudioPackage11.mk b/data/sounds/AudioPackage11.mk index b30be5689fb0..3c0929720449 100644 --- a/data/sounds/AudioPackage11.mk +++ b/data/sounds/AudioPackage11.mk @@ -17,19 +17,19 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \ $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \ $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \ $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \ $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \ $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \ $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \ - $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \ - $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \ + $(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:system/media/audio/ui/Lock.ogg \ + $(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:system/media/audio/ui/Unlock.ogg \ $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \ $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \ diff --git a/data/sounds/AudioPackage8.mk b/data/sounds/AudioPackage8.mk index 49b6154b77a2..0f4b8adb27c7 100644 --- a/data/sounds/AudioPackage8.mk +++ b/data/sounds/AudioPackage8.mk @@ -17,11 +17,11 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:system/media/audio/alarms/Plutonium.ogg \ $(LOCAL_PATH)/alarms/ogg/Promethium.ogg:system/media/audio/alarms/Promethium.ogg \ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:system/media/audio/ui/KeypressStandard.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:system/media/audio/ui/KeypressDelete.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \ $(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \ $(LOCAL_PATH)/effects/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \ diff --git a/data/sounds/AudioPackage9.mk b/data/sounds/AudioPackage9.mk index 87b7764a1743..36dc921501e4 100644 --- a/data/sounds/AudioPackage9.mk +++ b/data/sounds/AudioPackage9.mk @@ -17,11 +17,11 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:system/media/audio/ui/KeypressStandard.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:system/media/audio/ui/KeypressDelete.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \ $(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \ $(LOCAL_PATH)/effects/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \ diff --git a/data/sounds/notifications/ogg/Tethys_48k.ogg b/data/sounds/notifications/ogg/Tethys_48k.ogg Binary files differindex a9d8bbd7a6bf..355d522ce257 100644 --- a/data/sounds/notifications/ogg/Tethys_48k.ogg +++ b/data/sounds/notifications/ogg/Tethys_48k.ogg diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 6d60dd2812fe..3db12c502e53 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -67,6 +67,7 @@ public final class Bitmap implements Parcelable { * setPremultiplied() aren't order dependent, despite being setters. */ private boolean mIsPremultiplied; + private byte[] mNinePatchChunk; // may be null private int[] mLayoutBounds; // may be null private int mWidth; @@ -390,7 +391,7 @@ public final class Bitmap implements Parcelable { * No color information is stored. * With this configuration, each pixel requires 1 byte of memory. */ - ALPHA_8 (2), + ALPHA_8 (1), /** * Each pixel is stored on 2 bytes and only the RGB channels are @@ -406,7 +407,7 @@ public final class Bitmap implements Parcelable { * This configuration may be useful when using opaque bitmaps * that do not require high color fidelity. */ - RGB_565 (4), + RGB_565 (3), /** * Each pixel is stored on 2 bytes. The three RGB color channels @@ -428,7 +429,7 @@ public final class Bitmap implements Parcelable { * it is advised to use {@link #ARGB_8888} instead. */ @Deprecated - ARGB_4444 (5), + ARGB_4444 (4), /** * Each pixel is stored on 4 bytes. Each channel (RGB and alpha @@ -438,13 +439,13 @@ public final class Bitmap implements Parcelable { * This configuration is very flexible and offers the best * quality. It should be used whenever possible. */ - ARGB_8888 (6); + ARGB_8888 (5); final int nativeInt; @SuppressWarnings({"deprecation"}) private static Config sConfigs[] = { - null, null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888 + null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888 }; Config(int ni) { @@ -554,7 +555,7 @@ public final class Bitmap implements Parcelable { checkRecycled("Can't copy a recycled bitmap"); Bitmap b = nativeCopy(mNativeBitmap, config.nativeInt, isMutable); if (b != null) { - b.mIsPremultiplied = mIsPremultiplied; + b.setAlphaAndPremultiplied(hasAlpha(), mIsPremultiplied); b.mDensity = mDensity; } return b; @@ -727,12 +728,12 @@ public final class Bitmap implements Parcelable { paint.setAntiAlias(true); } } - + // The new bitmap was created from a known bitmap source so assume that // they use the same density bitmap.mDensity = source.mDensity; - bitmap.mIsPremultiplied = source.mIsPremultiplied; - + bitmap.setAlphaAndPremultiplied(source.hasAlpha(), source.mIsPremultiplied); + canvas.setBitmap(bitmap); canvas.drawBitmap(source, srcR, dstR, paint); canvas.setBitmap(null); @@ -810,9 +811,9 @@ public final class Bitmap implements Parcelable { if (display != null) { bm.mDensity = display.densityDpi; } + bm.setHasAlpha(hasAlpha); if (config == Config.ARGB_8888 && !hasAlpha) { nativeErase(bm.mNativeBitmap, 0xff000000); - nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha); } // No need to initialize the bitmap to zeroes with other configs; // it is backed by a VM byte array which is by definition preinitialized @@ -1041,11 +1042,23 @@ public final class Bitmap implements Parcelable { * <p>This method will not affect the behavior of a bitmap without an alpha * channel, or if {@link #hasAlpha()} returns false.</p> * + * <p>Calling createBitmap() or createScaledBitmap() with a source + * Bitmap whose colors are not pre-multiplied may result in a RuntimeException, + * since those functions require drawing the source, which is not supported for + * un-pre-multiplied Bitmaps.</p> + * * @see Bitmap#isPremultiplied() * @see BitmapFactory.Options#inPremultiplied */ public final void setPremultiplied(boolean premultiplied) { mIsPremultiplied = premultiplied; + nativeSetAlphaAndPremultiplied(mNativeBitmap, hasAlpha(), premultiplied); + } + + /** Helper function to set both alpha and premultiplied. **/ + private final void setAlphaAndPremultiplied(boolean hasAlpha, boolean premultiplied) { + mIsPremultiplied = premultiplied; + nativeSetAlphaAndPremultiplied(mNativeBitmap, hasAlpha, premultiplied); } /** Returns the bitmap's width */ @@ -1167,6 +1180,11 @@ public final class Bitmap implements Parcelable { * @see #reconfigure(int, int, Config) */ public final int getAllocationByteCount() { + if (mBuffer == null) { + // native backed bitmaps don't support reconfiguration, + // so alloc size is always content size + return getByteCount(); + } return mBuffer.length; } @@ -1201,7 +1219,7 @@ public final class Bitmap implements Parcelable { * non-opaque per-pixel alpha values. */ public void setHasAlpha(boolean hasAlpha) { - nativeSetHasAlpha(mNativeBitmap, hasAlpha); + nativeSetAlphaAndPremultiplied(mNativeBitmap, hasAlpha, mIsPremultiplied); } /** @@ -1606,7 +1624,8 @@ public final class Bitmap implements Parcelable { private static native void nativePrepareToDraw(int nativeBitmap); private static native boolean nativeHasAlpha(int nativeBitmap); - private static native void nativeSetHasAlpha(int nBitmap, boolean hasAlpha); + private static native void nativeSetAlphaAndPremultiplied(int nBitmap, boolean hasAlpha, + boolean isPremul); private static native boolean nativeHasMipMap(int nativeBitmap); private static native void nativeSetHasMipMap(int nBitmap, boolean hasMipMap); private static native boolean nativeSameAs(int nb0, int nb1); diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 429be492bd83..45753645835f 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -153,8 +153,12 @@ public class BitmapFactory { * * <p>This does not affect bitmaps without an alpha channel.</p> * + * <p>Setting this flag to false while setting {@link #inScaled} to true + * may result in incorrect colors.</p> + * * @see Bitmap#hasAlpha() * @see Bitmap#isPremultiplied() + * @see #inScaled */ public boolean inPremultiplied; @@ -249,6 +253,9 @@ public class BitmapFactory { * <p>This flag is turned on by default and should be turned off if you need * a non-scaled version of the bitmap. Nine-patch bitmaps ignore this * flag and are always scaled. + * + * <p>If {@link #inPremultiplied} is set to false, and the image has alpha, + * setting this flag to true may result in incorrect colors. */ public boolean inScaled; diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index e350e8d0c71c..aac78766514e 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -23,6 +23,7 @@ import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.SystemClock; +import android.util.LayoutDirection; import android.util.SparseArray; /** @@ -59,6 +60,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { private long mExitAnimationEnd; private Drawable mLastDrawable; + private Insets mInsets = Insets.NONE; + // overrides from Drawable @Override @@ -78,18 +81,31 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { | mDrawableContainerState.mChildrenChangingConfigurations; } + private boolean needsMirroring() { + return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; + } + @Override public boolean getPadding(Rect padding) { final Rect r = mDrawableContainerState.getConstantPadding(); + boolean result; if (r != null) { padding.set(r); - return true; - } - if (mCurrDrawable != null) { - return mCurrDrawable.getPadding(padding); + result = (r.left | r.top | r.bottom | r.right) != 0; } else { - return super.getPadding(padding); + if (mCurrDrawable != null) { + result = mCurrDrawable.getPadding(padding); + } else { + result = super.getPadding(padding); + } } + if (needsMirroring()) { + final int left = padding.left; + final int right = padding.right; + padding.left = right; + padding.right = left; + } + return result; } /** @@ -97,7 +113,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { */ @Override public Insets getOpticalInsets() { - return (mCurrDrawable == null) ? Insets.NONE : mCurrDrawable.getOpticalInsets(); + return mInsets; } @Override @@ -334,6 +350,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mCurrDrawable = d; mCurIndex = idx; if (d != null) { + mInsets = d.getOpticalInsets(); d.mutate(); if (mDrawableContainerState.mEnterFadeDuration > 0) { mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; @@ -348,9 +365,12 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { d.setBounds(getBounds()); d.setLayoutDirection(getLayoutDirection()); d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); + } else { + mInsets = Insets.NONE; } } else { mCurrDrawable = null; + mInsets = Insets.NONE; mCurIndex = -1; } diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index ab34c0fd5649..9c57a2cec25c 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -244,7 +244,7 @@ public class NinePatchDrawable extends Drawable { } else { padding.set(mPadding); } - return true; + return (padding.left | padding.top | padding.right | padding.bottom) != 0; } /** diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java index 81e523d734c4..29d14a2cd8b5 100644 --- a/graphics/java/android/graphics/pdf/PdfDocument.java +++ b/graphics/java/android/graphics/pdf/PdfDocument.java @@ -18,6 +18,7 @@ package android.graphics.pdf; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Rect; import dalvik.system.CloseGuard; @@ -69,6 +70,12 @@ import java.util.List; */ public class PdfDocument { + // TODO: We need a constructor that will take an OutputStream to + // support online data serialization as opposed to the current + // on demand one. The current approach is fine until Skia starts + // to support online PDF generation at which point we need to + // handle this. + private final byte[] mChunk = new byte[4096]; private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -111,7 +118,7 @@ public class PdfDocument { if (pageInfo == null) { throw new IllegalArgumentException("page cannot be null"); } - Canvas canvas = new PdfCanvas(nativeCreatePage(pageInfo.mPageWidth, + Canvas canvas = new PdfCanvas(nativeStartPage(mNativeDocument, pageInfo.mPageWidth, pageInfo.mPageHeight, pageInfo.mContentRect.left, pageInfo.mContentRect.top, pageInfo.mContentRect.right, pageInfo.mContentRect.bottom)); mCurrentPage = new Page(canvas, pageInfo); @@ -142,7 +149,7 @@ public class PdfDocument { } mPages.add(page.getInfo()); mCurrentPage = null; - nativeAppendPage(mNativeDocument, page.mCanvas.mNativeCanvas); + nativeFinishPage(mNativeDocument); page.finish(); } @@ -204,7 +211,7 @@ public class PdfDocument { private void dispose() { if (mNativeDocument != 0) { - nativeFinalize(mNativeDocument); + nativeClose(mNativeDocument); mCloseGuard.close(); mNativeDocument = 0; } @@ -230,14 +237,14 @@ public class PdfDocument { private native int nativeCreateDocument(); - private native void nativeFinalize(int document); + private native void nativeClose(int document); - private native void nativeAppendPage(int document, int page); + private native void nativeFinishPage(int document); private native void nativeWriteTo(int document, OutputStream out, byte[] chunk); - private static native int nativeCreatePage(int pageWidth, int pageHeight, int contentLeft, - int contentTop, int contentRight, int contentBottom); + private static native int nativeStartPage(int documentPtr, int pageWidth, int pageHeight, + int contentLeft, int contentTop, int contentRight, int contentBottom); private final class PdfCanvas extends Canvas { @@ -392,28 +399,28 @@ public class PdfDocument { * Gets the {@link Canvas} of the page. * * <p> - * <strong>Note: </strong> There are some draw operations that are - * not yet supported by the canvas returned by this method. More - * specifically: + * <strong>Note: </strong> There are some draw operations that are not yet + * supported by the canvas returned by this method. More specifically: * <ul> - * <li>{@link Canvas#clipPath(android.graphics.Path) - * Canvas.clipPath(android.graphics.Path)}</li> - * <li>All flavors of {@link Canvas#drawText(String, float, float, - * android.graphics.Paint) Canvas.drawText(String, float, float, - * android.graphics.Paint)}</li> - * <li>All flavors of {@link Canvas#drawPosText(String, float[], - * android.graphics.Paint) Canvas.drawPosText(String, float[], - * android.graphics.Paint)}</li> + * <li>Inverse path clipping performed via {@link Canvas#clipPath(android.graphics.Path, + * android.graphics.Region.Op) Canvas.clipPath(android.graphics.Path, + * android.graphics.Region.Op)} for {@link + * android.graphics.Region.Op#REVERSE_DIFFERENCE + * Region.Op#REVERSE_DIFFERENCE} operations.</li> * <li>{@link Canvas#drawVertices(android.graphics.Canvas.VertexMode, int, * float[], int, float[], int, int[], int, short[], int, int, * android.graphics.Paint) Canvas.drawVertices( * android.graphics.Canvas.VertexMode, int, float[], int, float[], * int, int[], int, short[], int, int, android.graphics.Paint)}</li> - * <li>{@link android.graphics.PorterDuff.Mode#SRC_ATOP PorterDuff.Mode SRC}, + * <li>Color filters set via {@link Paint#setColorFilter( + * android.graphics.ColorFilter)}</li> + * <li>Mask filters set via {@link Paint#setMaskFilter( + * android.graphics.MaskFilter)}</li> + * <li>Some XFER modes such as + * {@link android.graphics.PorterDuff.Mode#SRC_ATOP PorterDuff.Mode SRC}, * {@link android.graphics.PorterDuff.Mode#DST_ATOP PorterDuff.DST_ATOP}, * {@link android.graphics.PorterDuff.Mode#XOR PorterDuff.XOR}, * {@link android.graphics.PorterDuff.Mode#ADD PorterDuff.ADD}</li> - * <li>Perspective transforms</li> * </ul> * * @return The canvas if the page is not finished, null otherwise. diff --git a/graphics/java/android/renderscript/AllocationAdapter.java b/graphics/java/android/renderscript/AllocationAdapter.java index a6645bb591dc..84a9ad3b1e92 100644 --- a/graphics/java/android/renderscript/AllocationAdapter.java +++ b/graphics/java/android/renderscript/AllocationAdapter.java @@ -224,7 +224,6 @@ public class AllocationAdapter extends Allocation { } static public AllocationAdapter create2D(RenderScript rs, Allocation a) { - android.util.Log.e("rs", "create2d " + a); rs.validate(); AllocationAdapter aa = new AllocationAdapter(0, rs, a); aa.mConstrainedLOD = true; diff --git a/graphics/java/android/renderscript/BaseObj.java b/graphics/java/android/renderscript/BaseObj.java index e17d79a24161..fc54532cf865 100644 --- a/graphics/java/android/renderscript/BaseObj.java +++ b/graphics/java/android/renderscript/BaseObj.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.util.Log; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * BaseObj is the base class for all RenderScript objects owned by a RS context. @@ -109,17 +109,31 @@ public class BaseObj { return mName; } - protected void finalize() throws Throwable { - if (!mDestroyed) { - if(mID != 0 && mRS.isAlive()) { + private void helpDestroy() { + boolean shouldDestroy = false; + synchronized(this) { + if (!mDestroyed) { + shouldDestroy = true; + mDestroyed = true; + } + } + + if (shouldDestroy) { + // must include nObjDestroy in the critical section + ReentrantReadWriteLock.ReadLock rlock = mRS.mRWLock.readLock(); + rlock.lock(); + // AllocationAdapters are BaseObjs with an ID of 0 but should not be passed to nObjDestroy + if(mRS.isAlive() && mID != 0) { mRS.nObjDestroy(mID); } + rlock.unlock(); mRS = null; mID = 0; - mDestroyed = true; - //Log.v(RenderScript.LOG_TAG, getClass() + - // " auto finalizing object without having released the RS reference."); } + } + + protected void finalize() throws Throwable { + helpDestroy(); super.finalize(); } @@ -128,12 +142,11 @@ public class BaseObj { * primary use is to force immediate cleanup of resources when it is * believed the GC will not respond quickly enough. */ - synchronized public void destroy() { + public void destroy() { if(mDestroyed) { throw new RSInvalidStateException("Object already destroyed."); } - mDestroyed = true; - mRS.nObjDestroy(mID); + helpDestroy(); } /** diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index 7d4a5c4d1295..35fe8c498648 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -19,6 +19,7 @@ package android.renderscript; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.concurrent.locks.ReentrantReadWriteLock; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -102,6 +103,20 @@ public class RenderScript { static File mCacheDir; + // this should be a monotonically increasing ID + // used in conjunction with the API version of a device + static final long sMinorID = 1; + + /** + * Returns an identifier that can be used to identify a particular + * minor version of RS. + * + * @hide + */ + public static long getMinorID() { + return sMinorID; + } + /** * Sets the directory to use as a persistent storage for the * renderscript object file cache. @@ -151,6 +166,7 @@ public class RenderScript { } ContextType mContextType; + ReentrantReadWriteLock mRWLock; // Methods below are wrapped to protect the non-threadsafe // lockless fifo. @@ -178,7 +194,18 @@ public class RenderScript { native void rsnContextDestroy(int con); synchronized void nContextDestroy() { validate(); - rsnContextDestroy(mContext); + + // take teardown lock + // teardown lock can only be taken when no objects are being destroyed + ReentrantReadWriteLock.WriteLock wlock = mRWLock.writeLock(); + wlock.lock(); + + int curCon = mContext; + // context is considered dead as of this point + mContext = 0; + + wlock.unlock(); + rsnContextDestroy(curCon); } native void rsnContextSetSurface(int con, int w, int h, Surface sur); synchronized void nContextSetSurface(int w, int h, Surface sur) { @@ -263,8 +290,9 @@ public class RenderScript { validate(); return rsnGetName(mContext, obj); } + // nObjDestroy is explicitly _not_ synchronous to prevent crashes in finalizers native void rsnObjDestroy(int con, int id); - synchronized void nObjDestroy(int id) { + void nObjDestroy(int id) { // There is a race condition here. The calling code may be run // by the gc while teardown is occuring. This protects againts // deleting dead objects. @@ -1136,6 +1164,7 @@ public class RenderScript { if (ctx != null) { mApplicationContext = ctx.getApplicationContext(); } + mRWLock = new ReentrantReadWriteLock(); } /** @@ -1230,6 +1259,8 @@ public class RenderScript { */ public void destroy() { validate(); + nContextFinish(); + nContextDeinitToClient(mContext); mMessageThread.mRun = false; try { @@ -1238,7 +1269,6 @@ public class RenderScript { } nContextDestroy(); - mContext = 0; nDeviceDestroy(mDev); mDev = 0; diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp index 7814a01ad929..58f5325476eb 100644 --- a/libs/hwui/Program.cpp +++ b/libs/hwui/Program.cpp @@ -163,7 +163,7 @@ GLuint Program::buildShader(const char* source, GLenum type) { void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix, const mat4& transformMatrix, bool offset) { - if (projectionMatrix != mProjection) { + if (projectionMatrix != mProjection || offset != mOffset) { if (CC_LIKELY(!offset)) { glUniformMatrix4fv(projection, 1, GL_FALSE, &projectionMatrix.data[0]); } else { @@ -177,6 +177,7 @@ void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix, glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]); } mProjection = projectionMatrix; + mOffset = offset; } mat4 t(transformMatrix); diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h index 4f94afce9164..f6ac8ec76da8 100644 --- a/libs/hwui/Program.h +++ b/libs/hwui/Program.h @@ -431,6 +431,7 @@ private: bool mHasSampler; mat4 mProjection; + bool mOffset; }; // class Program }; // namespace uirenderer diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index ed0a79aac56a..8d0874f3e03b 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -285,10 +285,9 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege void TextureCache::uploadLoFiTexture(bool resize, SkBitmap* bitmap, uint32_t width, uint32_t height) { SkBitmap rgbaBitmap; - rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0, bitmap->alphaType()); rgbaBitmap.allocPixels(); rgbaBitmap.eraseColor(0); - rgbaBitmap.setIsOpaque(bitmap->isOpaque()); SkCanvas canvas(rgbaBitmap); canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL); diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index 18983d8b6d32..12a9c235c938 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -23,6 +23,7 @@ #include <utils/Trace.h> #include <SkGlyph.h> +#include <SkGlyphCache.h> #include <SkUtils.h> #include "FontUtil.h" @@ -271,9 +272,9 @@ CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool pre if (cachedGlyph) { // Is the glyph still in texture cache? if (!cachedGlyph->mIsValid) { - const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit, - &mDescription.mLookupTransform); - updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching); + SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform); + const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), textUnit); + updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), cachedGlyph, precaching); } } else { cachedGlyph = cacheGlyph(paint, textUnit, precaching); @@ -415,8 +416,8 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len } } -void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph, - bool precaching) { +void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, SkGlyphCache* skiaGlyphCache, + CachedGlyphInfo* glyph, bool precaching) { glyph->mAdvanceX = skiaGlyph.fAdvanceX; glyph->mAdvanceY = skiaGlyph.fAdvanceY; glyph->mBitmapLeft = skiaGlyph.fLeft; @@ -429,7 +430,7 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp // Get the bitmap for the glyph if (!skiaGlyph.fImage) { - paint->findImage(skiaGlyph, &mDescription.mLookupTransform); + skiaGlyphCache->findImage(skiaGlyph); } mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching); @@ -463,11 +464,12 @@ CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching CachedGlyphInfo* newGlyph = new CachedGlyphInfo(); mCachedGlyphs.add(glyph, newGlyph); - const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform); + SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform); + const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), glyph); newGlyph->mIsValid = false; newGlyph->mGlyphIndex = skiaGlyph.fID; - updateGlyphCache(paint, skiaGlyph, newGlyph, precaching); + updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), newGlyph, precaching); return newGlyph; } diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h index 9e7ec2dcfe4d..f68b43084187 100644 --- a/libs/hwui/font/Font.h +++ b/libs/hwui/font/Font.h @@ -19,6 +19,7 @@ #include <utils/KeyedVector.h> +#include <SkGlyphCache.h> #include <SkScalerContext.h> #include <SkPaint.h> #include <SkPathMeasure.h> @@ -117,8 +118,8 @@ private: void invalidateTextureCache(CacheTexture* cacheTexture = NULL); CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching); - void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph, - bool precaching); + void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, SkGlyphCache* skiaGlyphCache, + CachedGlyphInfo* glyph, bool precaching); void measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h index cdcb23c488e7..c2fd5f58e02d 100644 --- a/libs/hwui/font/FontUtil.h +++ b/libs/hwui/font/FontUtil.h @@ -40,7 +40,7 @@ #if RENDER_TEXT_AS_GLYPHS typedef uint16_t glyph_t; #define TO_GLYPH(g) g - #define GET_METRICS(paint, glyph, matrix) paint->getGlyphMetrics(glyph, matrix) + #define GET_METRICS(cache, glyph) cache->getGlyphIDMetrics(glyph) #define GET_GLYPH(text) nextGlyph((const uint16_t**) &text) #define IS_END_OF_STRING(glyph) false @@ -53,7 +53,7 @@ #else typedef SkUnichar glyph_t; #define TO_GLYPH(g) ((SkUnichar) g) - #define GET_METRICS(paint, glyph, matrix) paint->getUnicharMetrics(glyph, matrix) + #define GET_METRICS(cache, glyph) cache->getUnicharMetrics(glyph) #define GET_GLYPH(text) SkUTF16_NextUnichar((const uint16_t**) &text) #define IS_END_OF_STRING(glyph) glyph < 0 #endif diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 1f5fefd05fff..92474df7a603 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -49,6 +49,8 @@ import android.database.ContentObserver; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.net.Uri; +import android.net.http.CertificateChainValidator; +import android.net.http.SslError; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -81,10 +83,12 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParserException; +import java.io.ByteArrayInputStream; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Field; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; import java.util.HashMap; @@ -116,6 +120,8 @@ public class AudioService extends IAudioService.Stub { protected static final boolean DEBUG_RC = false; /** Debug volumes */ protected static final boolean DEBUG_VOL = false; + /** Debug cert verification */ + private static final boolean DEBUG_CERTS = false; /** How long to delay before persisting a change in volume/ringer mode. */ private static final int PERSIST_DELAY = 500; @@ -1019,7 +1025,7 @@ public class AudioService extends IAudioService.Stub { (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { synchronized (mA2dpAvrcpLock) { if (mA2dp != null && mAvrcpAbsVolSupported) { - mA2dp.setAvrcpAbsoluteVolume(index); + mA2dp.setAvrcpAbsoluteVolume(index / 10); } } } @@ -2913,12 +2919,10 @@ public class AudioService extends IAudioService.Stub { int index; if (isMuted()) { index = 0; - } else if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC && - (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) { index = (mIndexMax + 5)/10; - } - else { + } else { index = (getIndex(device) + 5)/10; } AudioSystem.setStreamVolumeIndex(mStreamType, index, device); @@ -2943,6 +2947,9 @@ public class AudioService extends IAudioService.Stub { if (device != AudioSystem.DEVICE_OUT_DEFAULT) { if (isMuted()) { index = 0; + } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + mAvrcpAbsVolSupported) { + index = (mIndexMax + 5)/10; } else { index = ((Integer)entry.getValue() + 5)/10; } @@ -3215,7 +3222,14 @@ public class AudioService extends IAudioService.Stub { for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { if (streamType != streamState.mStreamType && mStreamVolumeAlias[streamType] == streamState.mStreamType) { - mStreamStates[streamType].applyDeviceVolume(getDeviceForStream(streamType)); + // Make sure volume is also maxed out on A2DP device for aliased stream + // that may have a different device selected + int streamDevice = getDeviceForStream(streamType); + if ((device != streamDevice) && mAvrcpAbsVolSupported && + ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { + mStreamStates[streamType].applyDeviceVolume(device); + } + mStreamStates[streamType].applyDeviceVolume(streamDevice); } } @@ -3843,9 +3857,12 @@ public class AudioService extends IAudioService.Stub { // address is not used for now, but may be used when multiple a2dp devices are supported synchronized (mA2dpAvrcpLock) { mAvrcpAbsVolSupported = support; - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, streamState, 0); + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, + mStreamStates[AudioSystem.STREAM_MUSIC], 0); + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, + mStreamStates[AudioSystem.STREAM_RING], 0); } } @@ -4570,6 +4587,43 @@ public class AudioService extends IAudioService.Stub { } } + public int verifyX509CertChain(int numcerts, byte [] chain, String domain, String authType) { + + if (DEBUG_CERTS) { + Log.v(TAG, "java side verify for " + + numcerts + " certificates (" + chain.length + " bytes" + + ")for "+ domain + "/" + authType); + } + + byte[][] certChain = new byte[numcerts][]; + + ByteBuffer buf = ByteBuffer.wrap(chain); + for (int i = 0; i < numcerts; i++) { + int certlen = buf.getInt(); + if (DEBUG_CERTS) { + Log.i(TAG, "cert " + i +": " + certlen); + } + certChain[i] = new byte[certlen]; + buf.get(certChain[i]); + } + + try { + SslError err = CertificateChainValidator.verifyServerCertificates(certChain, + domain, authType); + if (DEBUG_CERTS) { + Log.i(TAG, "verified: " + err); + } + if (err == null) { + return -1; + } else { + return err.getPrimaryError(); + } + } catch (Exception e) { + Log.e(TAG, "failed to verify chain: " + e); + } + return SslError.SSL_INVALID; + } + //========================================================================================== // Camera shutter sound policy. diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 20eb35694146..9db35fce7db7 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -98,7 +98,7 @@ public class ExifInterface { private static SimpleDateFormat sFormatter; static { - System.loadLibrary("exif_jni"); + System.loadLibrary("jhead_jni"); sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); sFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 2f083256655d..b5c3631398aa 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -35,6 +35,8 @@ import android.view.KeyEvent; */ interface IAudioService { + int verifyX509CertChain(int chainsize, in byte[] chain, String host, String authtype); + void adjustVolume(int direction, int flags, String callingPackage); boolean isLocalOrRemoteMusicActive(); @@ -236,4 +238,5 @@ interface IAudioService { AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer); boolean isCameraSoundForced(); + } diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl new file mode 100644 index 000000000000..9640dcbd6518 --- /dev/null +++ b/media/java/android/media/IMediaRouterClient.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * {@hide} + */ +oneway interface IMediaRouterClient { + void onStateChanged(); +} diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl new file mode 100644 index 000000000000..f8f5fdfd2d62 --- /dev/null +++ b/media/java/android/media/IMediaRouterService.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.media.IMediaRouterClient; +import android.media.MediaRouterClientState; + +/** + * {@hide} + */ +interface IMediaRouterService { + void registerClientAsUser(IMediaRouterClient client, String packageName, int userId); + void unregisterClient(IMediaRouterClient client); + + MediaRouterClientState getState(IMediaRouterClient client); + + void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan); + void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit); + void requestSetVolume(IMediaRouterClient client, String routeId, int volume); + void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction); +} diff --git a/media/java/android/media/IRemoteDisplayCallback.aidl b/media/java/android/media/IRemoteDisplayCallback.aidl new file mode 100644 index 000000000000..19cf070aa08e --- /dev/null +++ b/media/java/android/media/IRemoteDisplayCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.media.RemoteDisplayState; + +/** + * {@hide} + */ +oneway interface IRemoteDisplayCallback { + void onStateChanged(in RemoteDisplayState state); +} diff --git a/media/java/android/media/IRemoteDisplayProvider.aidl b/media/java/android/media/IRemoteDisplayProvider.aidl new file mode 100644 index 000000000000..b0d7379d65f3 --- /dev/null +++ b/media/java/android/media/IRemoteDisplayProvider.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.media.IRemoteDisplayCallback; + +/** + * {@hide} + */ +oneway interface IRemoteDisplayProvider { + void setCallback(in IRemoteDisplayCallback callback); + void setDiscoveryMode(int mode); + void connect(String id); + void disconnect(String id); + void setVolume(String id, int volume); + void adjustVolume(String id, int delta); +} diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java index 59d411dd2b63..34008bb572b8 100644 --- a/media/java/android/media/MediaFocusControl.java +++ b/media/java/android/media/MediaFocusControl.java @@ -1930,7 +1930,6 @@ public class MediaFocusControl implements OnFinished { int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; synchronized(mAudioFocusLock) { synchronized(mRCStack) { - boolean wasCurrentRcController = isCurrentRcController(mediaIntent); // store the new display information try { for (int index = mRCStack.size()-1; index >= 0; index--) { @@ -1977,9 +1976,9 @@ public class MediaFocusControl implements OnFinished { Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); } - // if the eventReceiver is now at the top of the stack but wasn't before + // if the eventReceiver is at the top of the stack // then check for potential refresh of the remote controls - if (isCurrentRcController(mediaIntent) && !wasCurrentRcController) { + if (isCurrentRcController(mediaIntent)) { checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); } }//synchronized(mRCStack) diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 706258a9cd85..2c16b5eef634 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -3128,7 +3128,7 @@ public class MediaPlayer implements SubtitleController.Listener if (refreshTime || nanoTime >= mLastNanoTime + MAX_NS_WITHOUT_POSITION_CHECK) { try { - mLastTimeUs = mPlayer.getCurrentPosition() * 1000; + mLastTimeUs = mPlayer.getCurrentPosition() * 1000L; mPaused = !mPlayer.isPlaying(); if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs); } catch (IllegalStateException e) { diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index d73d81400482..de202278c40b 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -16,11 +16,15 @@ package android.media; +import com.android.internal.util.Objects; + +import android.Manifest; import android.app.ActivityThread; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; @@ -28,8 +32,10 @@ import android.hardware.display.WifiDisplay; import android.hardware.display.WifiDisplayStatus; import android.os.Handler; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.view.Display; @@ -52,14 +58,14 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public class MediaRouter { private static final String TAG = "MediaRouter"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); static class Static implements DisplayManager.DisplayListener { - // Time between wifi display scans when actively scanning in milliseconds. - private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000; - + final Context mAppContext; final Resources mResources; final IAudioService mAudioService; final DisplayManager mDisplayService; + final IMediaRouterService mMediaRouterService; final Handler mHandler; final CopyOnWriteArrayList<CallbackInfo> mCallbacks = new CopyOnWriteArrayList<CallbackInfo>(); @@ -76,8 +82,16 @@ public class MediaRouter { RouteInfo mSelectedRoute; - WifiDisplayStatus mLastKnownWifiDisplayStatus; + final boolean mCanConfigureWifiDisplays; boolean mActivelyScanningWifiDisplays; + String mPreviousActiveWifiDisplayAddress; + + int mDiscoveryRequestRouteTypes; + boolean mDiscoverRequestActiveScan; + + int mCurrentUserId = -1; + IMediaRouterClient mClient; + MediaRouterClientState mClientState; final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() { @Override @@ -90,17 +104,8 @@ public class MediaRouter { } }; - final Runnable mScanWifiDisplays = new Runnable() { - @Override - public void run() { - if (mActivelyScanningWifiDisplays) { - mDisplayService.scanWifiDisplays(); - mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL); - } - } - }; - Static(Context appContext) { + mAppContext = appContext; mResources = Resources.getSystem(); mHandler = new Handler(appContext.getMainLooper()); @@ -109,10 +114,20 @@ public class MediaRouter { mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE); + mMediaRouterService = IMediaRouterService.Stub.asInterface( + ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE)); + mSystemCategory = new RouteCategory( com.android.internal.R.string.default_audio_route_category_name, ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false); mSystemCategory.mIsSystem = true; + + // Only the system can configure wifi displays. The display manager + // enforces this with a permission check. Set a flag here so that we + // know whether this process is actually allowed to scan and connect. + mCanConfigureWifiDisplays = appContext.checkPermission( + Manifest.permission.CONFIGURE_WIFI_DISPLAY, + Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED; } // Called after sStatic is initialized @@ -120,8 +135,7 @@ public class MediaRouter { mDefaultAudioVideo = new RouteInfo(mSystemCategory); mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name; mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; - mDefaultAudioVideo.mPresentationDisplay = choosePresentationDisplayForRoute( - mDefaultAudioVideo, getAllPresentationDisplays()); + mDefaultAudioVideo.updatePresentationDisplay(); addRouteStatic(mDefaultAudioVideo); // This will select the active wifi display route if there is one. @@ -146,10 +160,13 @@ public class MediaRouter { updateAudioRoutes(newAudioRoutes); } + // Bind to the media router service. + rebindAsUser(UserHandle.myUserId()); + // Select the default route if the above didn't sync us up // appropriately with relevant system state. if (mSelectedRoute == null) { - selectRouteStatic(mDefaultAudioVideo.getSupportedTypes(), mDefaultAudioVideo); + selectDefaultRouteStatic(); } } @@ -197,7 +214,7 @@ public class MediaRouter { dispatchRouteChanged(sStatic.mBluetoothA2dpRoute); } } else if (sStatic.mBluetoothA2dpRoute != null) { - removeRoute(sStatic.mBluetoothA2dpRoute); + removeRouteStatic(sStatic.mBluetoothA2dpRoute); sStatic.mBluetoothA2dpRoute = null; } } @@ -205,38 +222,79 @@ public class MediaRouter { if (mBluetoothA2dpRoute != null) { if (mainType != AudioRoutesInfo.MAIN_SPEAKER && mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) { - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo); + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false); } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) && a2dpEnabled) { - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute); + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false); } } } - void updateActiveScan() { - if (hasActiveScanCallbackOfType(ROUTE_TYPE_LIVE_VIDEO)) { - if (!mActivelyScanningWifiDisplays) { - mActivelyScanningWifiDisplays = true; - mHandler.post(mScanWifiDisplays); - } - } else { - if (mActivelyScanningWifiDisplays) { - mActivelyScanningWifiDisplays = false; - mHandler.removeCallbacks(mScanWifiDisplays); - } - } - } - - private boolean hasActiveScanCallbackOfType(int type) { + void updateDiscoveryRequest() { + // What are we looking for today? + int routeTypes = 0; + int passiveRouteTypes = 0; + boolean activeScan = false; + boolean activeScanWifiDisplay = false; final int count = mCallbacks.size(); for (int i = 0; i < count; i++) { CallbackInfo cbi = mCallbacks.get(i); - if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0 - && (cbi.type & type) != 0) { - return true; + if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN + | CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0) { + // Discovery explicitly requested. + routeTypes |= cbi.type; + } else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0) { + // Discovery only passively requested. + passiveRouteTypes |= cbi.type; + } else { + // Legacy case since applications don't specify the discovery flag. + // Unfortunately we just have to assume they always need discovery + // whenever they have a callback registered. + routeTypes |= cbi.type; + } + if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) { + activeScan = true; + if ((cbi.type & ROUTE_TYPE_REMOTE_DISPLAY) != 0) { + activeScanWifiDisplay = true; + } } } - return false; + if (routeTypes != 0 || activeScan) { + // If someone else requests discovery then enable the passive listeners. + // This is used by the MediaRouteButton and MediaRouteActionProvider since + // they don't receive lifecycle callbacks from the Activity. + routeTypes |= passiveRouteTypes; + } + + // Update wifi display scanning. + // TODO: All of this should be managed by the media router service. + if (mCanConfigureWifiDisplays) { + if (mSelectedRoute != null + && mSelectedRoute.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) { + // Don't scan while already connected to a remote display since + // it may interfere with the ongoing transmission. + activeScanWifiDisplay = false; + } + if (activeScanWifiDisplay) { + if (!mActivelyScanningWifiDisplays) { + mActivelyScanningWifiDisplays = true; + mDisplayService.startWifiDisplayScan(); + } + } else { + if (mActivelyScanningWifiDisplays) { + mActivelyScanningWifiDisplays = false; + mDisplayService.stopWifiDisplayScan(); + } + } + } + + // Tell the media router service all about it. + if (routeTypes != mDiscoveryRequestRouteTypes + || activeScan != mDiscoverRequestActiveScan) { + mDiscoveryRequestRouteTypes = routeTypes; + mDiscoverRequestActiveScan = activeScan; + publishClientDiscoveryRequest(); + } } @Override @@ -259,18 +317,268 @@ public class MediaRouter { } private void updatePresentationDisplays(int changedDisplayId) { - final Display[] displays = getAllPresentationDisplays(); final int count = mRoutes.size(); for (int i = 0; i < count; i++) { - final RouteInfo info = mRoutes.get(i); - Display display = choosePresentationDisplayForRoute(info, displays); - if (display != info.mPresentationDisplay - || (display != null && display.getDisplayId() == changedDisplayId)) { - info.mPresentationDisplay = display; - dispatchRoutePresentationDisplayChanged(info); + final RouteInfo route = mRoutes.get(i); + if (route.updatePresentationDisplay() || (route.mPresentationDisplay != null + && route.mPresentationDisplay.getDisplayId() == changedDisplayId)) { + dispatchRoutePresentationDisplayChanged(route); + } + } + } + + void setSelectedRoute(RouteInfo info, boolean explicit) { + // Must be non-reentrant. + mSelectedRoute = info; + publishClientSelectedRoute(explicit); + } + + void rebindAsUser(int userId) { + if (mCurrentUserId != userId || userId < 0 || mClient == null) { + if (mClient != null) { + try { + mMediaRouterService.unregisterClient(mClient); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to unregister media router client.", ex); + } + mClient = null; + } + + mCurrentUserId = userId; + + try { + Client client = new Client(); + mMediaRouterService.registerClientAsUser(client, + mAppContext.getPackageName(), userId); + mClient = client; + } catch (RemoteException ex) { + Log.e(TAG, "Unable to register media router client.", ex); + } + + publishClientDiscoveryRequest(); + publishClientSelectedRoute(false); + updateClientState(); + } + } + + void publishClientDiscoveryRequest() { + if (mClient != null) { + try { + mMediaRouterService.setDiscoveryRequest(mClient, + mDiscoveryRequestRouteTypes, mDiscoverRequestActiveScan); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to publish media router client discovery request.", ex); + } + } + } + + void publishClientSelectedRoute(boolean explicit) { + if (mClient != null) { + try { + mMediaRouterService.setSelectedRoute(mClient, + mSelectedRoute != null ? mSelectedRoute.mGlobalRouteId : null, + explicit); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to publish media router client selected route.", ex); + } + } + } + + void updateClientState() { + // Update the client state. + mClientState = null; + if (mClient != null) { + try { + mClientState = mMediaRouterService.getState(mClient); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to retrieve media router client state.", ex); + } + } + final ArrayList<MediaRouterClientState.RouteInfo> globalRoutes = + mClientState != null ? mClientState.routes : null; + final String globallySelectedRouteId = mClientState != null ? + mClientState.globallySelectedRouteId : null; + + // Add or update routes. + final int globalRouteCount = globalRoutes != null ? globalRoutes.size() : 0; + for (int i = 0; i < globalRouteCount; i++) { + final MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(i); + RouteInfo route = findGlobalRoute(globalRoute.id); + if (route == null) { + route = makeGlobalRoute(globalRoute); + addRouteStatic(route); + } else { + updateGlobalRoute(route, globalRoute); + } + } + + // Synchronize state with the globally selected route. + if (globallySelectedRouteId != null) { + final RouteInfo route = findGlobalRoute(globallySelectedRouteId); + if (route == null) { + Log.w(TAG, "Could not find new globally selected route: " + + globallySelectedRouteId); + } else if (route != mSelectedRoute) { + if (DEBUG) { + Log.d(TAG, "Selecting new globally selected route: " + route); + } + selectRouteStatic(route.mSupportedTypes, route, false); + } + } else if (mSelectedRoute != null && mSelectedRoute.mGlobalRouteId != null) { + if (DEBUG) { + Log.d(TAG, "Unselecting previous globally selected route: " + mSelectedRoute); + } + selectDefaultRouteStatic(); + } + + // Remove defunct routes. + outer: for (int i = mRoutes.size(); i-- > 0; ) { + final RouteInfo route = mRoutes.get(i); + final String globalRouteId = route.mGlobalRouteId; + if (globalRouteId != null) { + for (int j = 0; j < globalRouteCount; j++) { + MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(j); + if (globalRouteId.equals(globalRoute.id)) { + continue outer; // found + } + } + // not found + removeRouteStatic(route); + } + } + } + + void requestSetVolume(RouteInfo route, int volume) { + if (route.mGlobalRouteId != null && mClient != null) { + try { + mMediaRouterService.requestSetVolume(mClient, + route.mGlobalRouteId, volume); + } catch (RemoteException ex) { + Log.w(TAG, "Unable to request volume change.", ex); } } } + + void requestUpdateVolume(RouteInfo route, int direction) { + if (route.mGlobalRouteId != null && mClient != null) { + try { + mMediaRouterService.requestUpdateVolume(mClient, + route.mGlobalRouteId, direction); + } catch (RemoteException ex) { + Log.w(TAG, "Unable to request volume change.", ex); + } + } + } + + RouteInfo makeGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) { + RouteInfo route = new RouteInfo(sStatic.mSystemCategory); + route.mGlobalRouteId = globalRoute.id; + route.mName = globalRoute.name; + route.mDescription = globalRoute.description; + route.mSupportedTypes = globalRoute.supportedTypes; + route.mEnabled = globalRoute.enabled; + route.setRealStatusCode(globalRoute.statusCode); + route.mPlaybackType = globalRoute.playbackType; + route.mPlaybackStream = globalRoute.playbackStream; + route.mVolume = globalRoute.volume; + route.mVolumeMax = globalRoute.volumeMax; + route.mVolumeHandling = globalRoute.volumeHandling; + route.mPresentationDisplayId = globalRoute.presentationDisplayId; + route.updatePresentationDisplay(); + return route; + } + + void updateGlobalRoute(RouteInfo route, MediaRouterClientState.RouteInfo globalRoute) { + boolean changed = false; + boolean volumeChanged = false; + boolean presentationDisplayChanged = false; + + if (!Objects.equal(route.mName, globalRoute.name)) { + route.mName = globalRoute.name; + changed = true; + } + if (!Objects.equal(route.mDescription, globalRoute.description)) { + route.mDescription = globalRoute.description; + changed = true; + } + final int oldSupportedTypes = route.mSupportedTypes; + if (oldSupportedTypes != globalRoute.supportedTypes) { + route.mSupportedTypes = globalRoute.supportedTypes; + changed = true; + } + if (route.mEnabled != globalRoute.enabled) { + route.mEnabled = globalRoute.enabled; + changed = true; + } + if (route.mRealStatusCode != globalRoute.statusCode) { + route.setRealStatusCode(globalRoute.statusCode); + changed = true; + } + if (route.mPlaybackType != globalRoute.playbackType) { + route.mPlaybackType = globalRoute.playbackType; + changed = true; + } + if (route.mPlaybackStream != globalRoute.playbackStream) { + route.mPlaybackStream = globalRoute.playbackStream; + changed = true; + } + if (route.mVolume != globalRoute.volume) { + route.mVolume = globalRoute.volume; + changed = true; + volumeChanged = true; + } + if (route.mVolumeMax != globalRoute.volumeMax) { + route.mVolumeMax = globalRoute.volumeMax; + changed = true; + volumeChanged = true; + } + if (route.mVolumeHandling != globalRoute.volumeHandling) { + route.mVolumeHandling = globalRoute.volumeHandling; + changed = true; + volumeChanged = true; + } + if (route.mPresentationDisplayId != globalRoute.presentationDisplayId) { + route.mPresentationDisplayId = globalRoute.presentationDisplayId; + route.updatePresentationDisplay(); + changed = true; + presentationDisplayChanged = true; + } + + if (changed) { + dispatchRouteChanged(route, oldSupportedTypes); + } + if (volumeChanged) { + dispatchRouteVolumeChanged(route); + } + if (presentationDisplayChanged) { + dispatchRoutePresentationDisplayChanged(route); + } + } + + RouteInfo findGlobalRoute(String globalRouteId) { + final int count = mRoutes.size(); + for (int i = 0; i < count; i++) { + final RouteInfo route = mRoutes.get(i); + if (globalRouteId.equals(route.mGlobalRouteId)) { + return route; + } + } + return null; + } + + final class Client extends IMediaRouterClient.Stub { + @Override + public void onStateChanged() { + mHandler.post(new Runnable() { + @Override + public void run() { + if (Client.this == mClient) { + updateClientState(); + } + } + }); + } + } } static Static sStatic; @@ -285,7 +593,7 @@ public class MediaRouter { * <p>Once initiated this routing is transparent to the application. All audio * played on the media stream will be routed to the selected destination.</p> */ - public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1; + public static final int ROUTE_TYPE_LIVE_AUDIO = 1 << 0; /** * Route type flag for live video. @@ -302,7 +610,13 @@ public class MediaRouter { * @see RouteInfo#getPresentationDisplay() * @see android.app.Presentation */ - public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2; + public static final int ROUTE_TYPE_LIVE_VIDEO = 1 << 1; + + /** + * Temporary interop constant to identify remote displays. + * @hide To be removed when media router API is updated. + */ + public static final int ROUTE_TYPE_REMOTE_DISPLAY = 1 << 2; /** * Route type flag for application-specific usage. @@ -312,7 +626,10 @@ public class MediaRouter { * is expected to interpret the meaning of these events and perform the requested * routing tasks.</p> */ - public static final int ROUTE_TYPE_USER = 0x00800000; + public static final int ROUTE_TYPE_USER = 1 << 23; + + static final int ROUTE_TYPE_ANY = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO + | ROUTE_TYPE_REMOTE_DISPLAY | ROUTE_TYPE_USER; /** * Flag for {@link #addCallback}: Actively scan for routes while this callback @@ -336,11 +653,40 @@ public class MediaRouter { * Flag for {@link #addCallback}: Do not filter route events. * <p> * When this flag is specified, the callback will be invoked for event that affect any - * route event if they do not match the callback's associated media route selector. + * route even if they do not match the callback's filter. * </p> */ public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1; + /** + * Explicitly requests discovery. + * + * @hide Future API ported from support library. Revisit this later. + */ + public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2; + + /** + * Requests that discovery be performed but only if there is some other active + * callback already registered. + * + * @hide Compatibility workaround for the fact that applications do not currently + * request discovery explicitly (except when using the support library API). + */ + public static final int CALLBACK_FLAG_PASSIVE_DISCOVERY = 1 << 3; + + /** + * Flag for {@link #isRouteAvailable}: Ignore the default route. + * <p> + * This flag is used to determine whether a matching non-default route is available. + * This constraint may be used to decide whether to offer the route chooser dialog + * to the user. There is no point offering the chooser if there are no + * non-default choices. + * </p> + * + * @hide Future API ported from support library. Revisit this later. + */ + public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0; + // Maps application contexts static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>(); @@ -352,6 +698,9 @@ public class MediaRouter { if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) { result.append("ROUTE_TYPE_LIVE_VIDEO "); } + if ((types & ROUTE_TYPE_REMOTE_DISPLAY) != 0) { + result.append("ROUTE_TYPE_REMOTE_DISPLAY "); + } if ((types & ROUTE_TYPE_USER) != 0) { result.append("ROUTE_TYPE_USER "); } @@ -388,6 +737,11 @@ public class MediaRouter { return sStatic.mSystemCategory; } + /** @hide */ + public RouteInfo getSelectedRoute() { + return getSelectedRoute(ROUTE_TYPE_ANY); + } + /** * Return the currently selected route for any of the given types * @@ -411,6 +765,38 @@ public class MediaRouter { } /** + * Returns true if there is a route that matches the specified types. + * <p> + * This method returns true if there are any available routes that match the types + * regardless of whether they are enabled or disabled. If the + * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then + * the method will only consider non-default routes. + * </p> + * + * @param types The types to match. + * @param flags Flags to control the determination of whether a route may be available. + * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}. + * @return True if a matching route may be available. + * + * @hide Future API ported from support library. Revisit this later. + */ + public boolean isRouteAvailable(int types, int flags) { + final int count = sStatic.mRoutes.size(); + for (int i = 0; i < count; i++) { + RouteInfo route = sStatic.mRoutes.get(i); + if (route.matchesTypes(types)) { + if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) == 0 + || route != sStatic.mDefaultAudioVideo) { + return true; + } + } + } + + // It doesn't look like we can find a matching route right now. + return false; + } + + /** * Add a callback to listen to events about specific kinds of media routes. * If the specified callback is already registered, its registration will be updated for any * additional route types specified. @@ -453,9 +839,7 @@ public class MediaRouter { info = new CallbackInfo(cb, types, flags, this); sStatic.mCallbacks.add(info); } - if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) { - sStatic.updateActiveScan(); - } + sStatic.updateDiscoveryRequest(); } /** @@ -466,10 +850,8 @@ public class MediaRouter { public void removeCallback(Callback cb) { int index = findCallbackInfo(cb); if (index >= 0) { - CallbackInfo info = sStatic.mCallbacks.remove(index); - if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) { - sStatic.updateActiveScan(); - } + sStatic.mCallbacks.remove(index); + sStatic.updateDiscoveryRequest(); } else { Log.w(TAG, "removeCallback(" + cb + "): callback not registered"); } @@ -499,20 +881,20 @@ public class MediaRouter { * @param route Route to select */ public void selectRoute(int types, RouteInfo route) { - selectRouteStatic(types, route); + selectRouteStatic(types, route, true); } - + /** * @hide internal use */ - public void selectRouteInt(int types, RouteInfo route) { - selectRouteStatic(types, route); + public void selectRouteInt(int types, RouteInfo route, boolean explicit) { + selectRouteStatic(types, route, explicit); } - static void selectRouteStatic(int types, RouteInfo route) { + static void selectRouteStatic(int types, RouteInfo route, boolean explicit) { final RouteInfo oldRoute = sStatic.mSelectedRoute; if (oldRoute == route) return; - if ((route.getSupportedTypes() & types) == 0) { + if (!route.matchesTypes(types)) { Log.w(TAG, "selectRoute ignored; cannot select route with supported types " + typesToString(route.getSupportedTypes()) + " into route types " + typesToString(types)); @@ -535,19 +917,44 @@ public class MediaRouter { final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null; if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) { if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) { - sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress); + if (sStatic.mCanConfigureWifiDisplays) { + sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress); + } else { + Log.e(TAG, "Cannot connect to wifi displays because this process " + + "is not allowed to do so."); + } } else if (activeDisplay != null && !newRouteHasAddress) { sStatic.mDisplayService.disconnectWifiDisplay(); } } + sStatic.setSelectedRoute(route, explicit); + if (oldRoute != null) { dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute); + if (oldRoute.resolveStatusCode()) { + dispatchRouteChanged(oldRoute); + } } - sStatic.mSelectedRoute = route; if (route != null) { + if (route.resolveStatusCode()) { + dispatchRouteChanged(route); + } dispatchRouteSelected(types & route.getSupportedTypes(), route); } + + // The behavior of active scans may depend on the currently selected route. + sStatic.updateDiscoveryRequest(); + } + + static void selectDefaultRouteStatic() { + // TODO: Be smarter about the route types here; this selects for all valid. + if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute + && sStatic.mBluetoothA2dpRoute != null) { + selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false); + } else { + selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false); + } } /** @@ -612,7 +1019,7 @@ public class MediaRouter { * @see #addUserRoute(UserRouteInfo) */ public void removeUserRoute(UserRouteInfo info) { - removeRoute(info); + removeRouteStatic(info); } /** @@ -626,7 +1033,7 @@ public class MediaRouter { // TODO Right now, RouteGroups only ever contain user routes. // The code below will need to change if this assumption does. if (info instanceof UserRouteInfo || info instanceof RouteGroup) { - removeRouteAt(i); + removeRouteStatic(info); i--; } } @@ -636,10 +1043,10 @@ public class MediaRouter { * @hide internal use only */ public void removeRouteInt(RouteInfo info) { - removeRoute(info); + removeRouteStatic(info); } - static void removeRoute(RouteInfo info) { + static void removeRouteStatic(RouteInfo info) { if (sStatic.mRoutes.remove(info)) { final RouteCategory removingCat = info.getCategory(); final int count = sStatic.mRoutes.size(); @@ -651,42 +1058,9 @@ public class MediaRouter { break; } } - if (info == sStatic.mSelectedRoute) { + if (info.isSelected()) { // Removing the currently selected route? Select the default before we remove it. - // TODO: Be smarter about the route types here; this selects for all valid. - if (info != sStatic.mBluetoothA2dpRoute && sStatic.mBluetoothA2dpRoute != null) { - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, - sStatic.mBluetoothA2dpRoute); - } else { - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, - sStatic.mDefaultAudioVideo); - } - } - if (!found) { - sStatic.mCategories.remove(removingCat); - } - dispatchRouteRemoved(info); - } - } - - void removeRouteAt(int routeIndex) { - if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) { - final RouteInfo info = sStatic.mRoutes.remove(routeIndex); - final RouteCategory removingCat = info.getCategory(); - final int count = sStatic.mRoutes.size(); - boolean found = false; - for (int i = 0; i < count; i++) { - final RouteCategory cat = sStatic.mRoutes.get(i).getCategory(); - if (removingCat == cat) { - found = true; - break; - } - } - if (info == sStatic.mSelectedRoute) { - // Removing the currently selected route? Select the default before we remove it. - // TODO: Be smarter about the route types here; this selects for all valid. - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER, - sStatic.mDefaultAudioVideo); + selectDefaultRouteStatic(); } if (!found) { sStatic.mCategories.remove(removingCat); @@ -780,6 +1154,23 @@ public class MediaRouter { return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable); } + /** + * Rebinds the media router to handle routes that belong to the specified user. + * Requires the interact across users permission to access the routes of another user. + * <p> + * This method is a complete hack to work around the singleton nature of the + * media router when running inside of singleton processes like QuickSettings. + * This mechanism should be burned to the ground when MediaRouter is redesigned. + * Ideally the current user would be pulled from the Context but we need to break + * down MediaRouter.Static before we can get there. + * </p> + * + * @hide + */ + public void rebindAsUser(int userId) { + sStatic.rebindAsUser(userId); + } + static void updateRoute(final RouteInfo info) { dispatchRouteChanged(info); } @@ -801,10 +1192,34 @@ public class MediaRouter { } static void dispatchRouteChanged(RouteInfo info) { + dispatchRouteChanged(info, info.mSupportedTypes); + } + + static void dispatchRouteChanged(RouteInfo info, int oldSupportedTypes) { + final int newSupportedTypes = info.mSupportedTypes; for (CallbackInfo cbi : sStatic.mCallbacks) { - if (cbi.filterRouteEvent(info)) { + // Reconstruct some of the history for callbacks that may not have observed + // all of the events needed to correctly interpret the current state. + // FIXME: This is a strong signal that we should deprecate route type filtering + // completely in the future because it can lead to inconsistencies in + // applications. + final boolean oldVisibility = cbi.filterRouteEvent(oldSupportedTypes); + final boolean newVisibility = cbi.filterRouteEvent(newSupportedTypes); + if (!oldVisibility && newVisibility) { + cbi.cb.onRouteAdded(cbi.router, info); + if (info.isSelected()) { + cbi.cb.onRouteSelected(cbi.router, newSupportedTypes, info); + } + } + if (oldVisibility || newVisibility) { cbi.cb.onRouteChanged(cbi.router, info); } + if (oldVisibility && !newVisibility) { + if (info.isSelected()) { + cbi.cb.onRouteUnselected(cbi.router, oldSupportedTypes, info); + } + cbi.cb.onRouteRemoved(cbi.router, info); + } } } @@ -875,65 +1290,73 @@ public class MediaRouter { } } - static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) { - final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus; - - // TODO Naive implementation. Make this smarter later. - boolean wantScan = false; - boolean blockScan = false; - WifiDisplay[] oldDisplays = oldStatus != null ? - oldStatus.getDisplays() : WifiDisplay.EMPTY_ARRAY; - WifiDisplay[] newDisplays; + static void updateWifiDisplayStatus(WifiDisplayStatus status) { + WifiDisplay[] displays; WifiDisplay activeDisplay; - - if (newStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { - newDisplays = newStatus.getDisplays(); - activeDisplay = newStatus.getActiveDisplay(); + if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { + displays = status.getDisplays(); + activeDisplay = status.getActiveDisplay(); + + // Only the system is able to connect to wifi display routes. + // The display manager will enforce this with a permission check but it + // still publishes information about all available displays. + // Filter the list down to just the active display. + if (!sStatic.mCanConfigureWifiDisplays) { + if (activeDisplay != null) { + displays = new WifiDisplay[] { activeDisplay }; + } else { + displays = WifiDisplay.EMPTY_ARRAY; + } + } } else { - newDisplays = WifiDisplay.EMPTY_ARRAY; + displays = WifiDisplay.EMPTY_ARRAY; activeDisplay = null; } + String activeDisplayAddress = activeDisplay != null ? + activeDisplay.getDeviceAddress() : null; - for (int i = 0; i < newDisplays.length; i++) { - final WifiDisplay d = newDisplays[i]; - if (d.isRemembered()) { + // Add or update routes. + for (int i = 0; i < displays.length; i++) { + final WifiDisplay d = displays[i]; + if (shouldShowWifiDisplay(d, activeDisplay)) { RouteInfo route = findWifiDisplayRoute(d); if (route == null) { - route = makeWifiDisplayRoute(d, newStatus); + route = makeWifiDisplayRoute(d, status); addRouteStatic(route); - wantScan = true; } else { - updateWifiDisplayRoute(route, d, newStatus); + String address = d.getDeviceAddress(); + boolean disconnected = !address.equals(activeDisplayAddress) + && address.equals(sStatic.mPreviousActiveWifiDisplayAddress); + updateWifiDisplayRoute(route, d, status, disconnected); } if (d.equals(activeDisplay)) { - selectRouteStatic(route.getSupportedTypes(), route); - - // Don't scan if we're already connected to a wifi display, - // the scanning process can cause a hiccup with some configurations. - blockScan = true; + selectRouteStatic(route.getSupportedTypes(), route, false); } } } - for (int i = 0; i < oldDisplays.length; i++) { - final WifiDisplay d = oldDisplays[i]; - if (d.isRemembered()) { - final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays); - if (newDisplay == null || !newDisplay.isRemembered()) { - removeRoute(findWifiDisplayRoute(d)); + + // Remove stale routes. + for (int i = sStatic.mRoutes.size(); i-- > 0; ) { + RouteInfo route = sStatic.mRoutes.get(i); + if (route.mDeviceAddress != null) { + WifiDisplay d = findWifiDisplay(displays, route.mDeviceAddress); + if (d == null || !shouldShowWifiDisplay(d, activeDisplay)) { + removeRouteStatic(route); } } } - if (wantScan && !blockScan) { - sStatic.mDisplayService.scanWifiDisplays(); - } + // Remember the current active wifi display address so that we can infer disconnections. + // TODO: This hack will go away once all of this is moved into the media router service. + sStatic.mPreviousActiveWifiDisplayAddress = activeDisplayAddress; + } - sStatic.mLastKnownWifiDisplayStatus = newStatus; + private static boolean shouldShowWifiDisplay(WifiDisplay d, WifiDisplay activeDisplay) { + return d.isRemembered() || d.equals(activeDisplay); } static int getWifiDisplayStatusCode(WifiDisplay d, WifiDisplayStatus wfdStatus) { - int newStatus = RouteInfo.STATUS_NONE; - + int newStatus; if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) { newStatus = RouteInfo.STATUS_SCANNING; } else if (d.isAvailable()) { @@ -947,7 +1370,7 @@ public class MediaRouter { final int activeState = wfdStatus.getActiveDisplayState(); switch (activeState) { case WifiDisplayStatus.DISPLAY_STATE_CONNECTED: - newStatus = RouteInfo.STATUS_NONE; + newStatus = RouteInfo.STATUS_CONNECTED; break; case WifiDisplayStatus.DISPLAY_STATE_CONNECTING: newStatus = RouteInfo.STATUS_CONNECTING; @@ -968,23 +1391,23 @@ public class MediaRouter { static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) { final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory); newRoute.mDeviceAddress = display.getDeviceAddress(); - newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; + newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO + | ROUTE_TYPE_REMOTE_DISPLAY; newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED; newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE; - newRoute.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); + newRoute.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); newRoute.mEnabled = isWifiDisplayEnabled(display, wfdStatus); newRoute.mName = display.getFriendlyDisplayName(); newRoute.mDescription = sStatic.mResources.getText( com.android.internal.R.string.wireless_display_route_description); - - newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute, - sStatic.getAllPresentationDisplays()); + newRoute.updatePresentationDisplay(); return newRoute; } private static void updateWifiDisplayRoute( - RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus) { + RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus, + boolean disconnected) { boolean changed = false; final String newName = display.getFriendlyDisplayName(); if (!route.getName().equals(newName)) { @@ -996,24 +1419,23 @@ public class MediaRouter { changed |= route.mEnabled != enabled; route.mEnabled = enabled; - changed |= route.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); + changed |= route.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); if (changed) { dispatchRouteChanged(route); } - if (!enabled && route == sStatic.mSelectedRoute) { + if ((!enabled || disconnected) && route.isSelected()) { // Oops, no longer available. Reselect the default. - final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo; - selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute); + selectDefaultRouteStatic(); } } - private static WifiDisplay findMatchingDisplay(WifiDisplay d, WifiDisplay[] displays) { + private static WifiDisplay findWifiDisplay(WifiDisplay[] displays, String deviceAddress) { for (int i = 0; i < displays.length; i++) { - final WifiDisplay other = displays[i]; - if (d.hasSameAddress(other)) { - return other; + final WifiDisplay d = displays[i]; + if (d.getDeviceAddress().equals(deviceAddress)) { + return d; } } return null; @@ -1030,27 +1452,6 @@ public class MediaRouter { return null; } - private static Display choosePresentationDisplayForRoute(RouteInfo route, Display[] displays) { - if ((route.mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) { - if (route.mDeviceAddress != null) { - // Find the indicated Wifi display by its address. - for (Display display : displays) { - if (display.getType() == Display.TYPE_WIFI - && route.mDeviceAddress.equals(display.getAddress())) { - return display; - } - } - return null; - } - - if (route == sStatic.mDefaultAudioVideo && displays.length > 0) { - // Choose the first presentation display from the list. - return displays[0]; - } - } - return null; - } - /** * Information about a media route. */ @@ -1071,12 +1472,18 @@ public class MediaRouter { int mPlaybackStream = AudioManager.STREAM_MUSIC; VolumeCallbackInfo mVcb; Display mPresentationDisplay; + int mPresentationDisplayId = -1; String mDeviceAddress; boolean mEnabled = true; + // An id by which the route is known to the media router service. + // Null if this route only exists as an artifact within this process. + String mGlobalRouteId; + // A predetermined connection status that can override mStatus - private int mStatusCode; + private int mRealStatusCode; + private int mResolvedStatusCode; /** @hide */ public static final int STATUS_NONE = 0; /** @hide */ public static final int STATUS_SCANNING = 1; @@ -1084,19 +1491,20 @@ public class MediaRouter { /** @hide */ public static final int STATUS_AVAILABLE = 3; /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4; /** @hide */ public static final int STATUS_IN_USE = 5; + /** @hide */ public static final int STATUS_CONNECTED = 6; private Object mTag; /** * The default playback type, "local", indicating the presentation of the media is happening * on the same device (e.g. a phone, a tablet) as where it is controlled from. - * @see #setPlaybackType(int) + * @see #getPlaybackType() */ public final static int PLAYBACK_TYPE_LOCAL = 0; /** * A playback type indicating the presentation of the media is happening on * a different device (i.e. the remote device) than where it is controlled from. - * @see #setPlaybackType(int) + * @see #getPlaybackType() */ public final static int PLAYBACK_TYPE_REMOTE = 1; /** @@ -1104,12 +1512,13 @@ public class MediaRouter { * controlled from this object. An example of fixed playback volume is a remote player, * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather * than attenuate at the source. - * @see #setVolumeHandling(int) + * @see #getVolumeHandling() */ public final static int PLAYBACK_VOLUME_FIXED = 0; /** * Playback information indicating the playback volume is variable and can be controlled * from this object. + * @see #getVolumeHandling() */ public final static int PLAYBACK_VOLUME_VARIABLE = 1; @@ -1178,38 +1587,71 @@ public class MediaRouter { * Set this route's status by predetermined status code. If the caller * should dispatch a route changed event this call will return true; */ - boolean setStatusCode(int statusCode) { - if (statusCode != mStatusCode) { - mStatusCode = statusCode; - int resId = 0; + boolean setRealStatusCode(int statusCode) { + if (mRealStatusCode != statusCode) { + mRealStatusCode = statusCode; + return resolveStatusCode(); + } + return false; + } + + /** + * Resolves the status code whenever the real status code or selection state + * changes. + */ + boolean resolveStatusCode() { + int statusCode = mRealStatusCode; + if (isSelected()) { switch (statusCode) { - case STATUS_SCANNING: - resId = com.android.internal.R.string.media_route_status_scanning; - break; - case STATUS_CONNECTING: - resId = com.android.internal.R.string.media_route_status_connecting; - break; + // If the route is selected and its status appears to be between states + // then report it as connecting even though it has not yet had a chance + // to officially move into the CONNECTING state. Note that routes in + // the NONE state are assumed to not require an explicit connection + // lifecycle whereas those that are AVAILABLE are assumed to have + // to eventually proceed to CONNECTED. case STATUS_AVAILABLE: - resId = com.android.internal.R.string.media_route_status_available; - break; - case STATUS_NOT_AVAILABLE: - resId = com.android.internal.R.string.media_route_status_not_available; - break; - case STATUS_IN_USE: - resId = com.android.internal.R.string.media_route_status_in_use; + case STATUS_SCANNING: + statusCode = STATUS_CONNECTING; break; } - mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null; - return true; } - return false; + if (mResolvedStatusCode == statusCode) { + return false; + } + + mResolvedStatusCode = statusCode; + int resId; + switch (statusCode) { + case STATUS_SCANNING: + resId = com.android.internal.R.string.media_route_status_scanning; + break; + case STATUS_CONNECTING: + resId = com.android.internal.R.string.media_route_status_connecting; + break; + case STATUS_AVAILABLE: + resId = com.android.internal.R.string.media_route_status_available; + break; + case STATUS_NOT_AVAILABLE: + resId = com.android.internal.R.string.media_route_status_not_available; + break; + case STATUS_IN_USE: + resId = com.android.internal.R.string.media_route_status_in_use; + break; + case STATUS_CONNECTED: + case STATUS_NONE: + default: + resId = 0; + break; + } + mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null; + return true; } /** * @hide */ public int getStatusCode() { - return mStatusCode; + return mResolvedStatusCode; } /** @@ -1219,6 +1661,11 @@ public class MediaRouter { return mSupportedTypes; } + /** @hide */ + public boolean matchesTypes(int types) { + return (mSupportedTypes & types) != 0; + } + /** * @return The group that this route belongs to. */ @@ -1317,9 +1764,7 @@ public class MediaRouter { Log.e(TAG, "Error setting local stream volume", e); } } else { - Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " + - "Non-local volume playback on system route? " + - "Could not request volume change."); + sStatic.requestSetVolume(this, volume); } } @@ -1338,9 +1783,7 @@ public class MediaRouter { Log.e(TAG, "Error setting local stream volume", e); } } else { - Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " + - "Non-local volume playback on system route? " + - "Could not request volume change."); + sStatic.requestUpdateVolume(this, direction); } } @@ -1402,6 +1845,55 @@ public class MediaRouter { return mPresentationDisplay; } + boolean updatePresentationDisplay() { + Display display = choosePresentationDisplay(); + if (mPresentationDisplay != display) { + mPresentationDisplay = display; + return true; + } + return false; + } + + private Display choosePresentationDisplay() { + if ((mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) { + Display[] displays = sStatic.getAllPresentationDisplays(); + + // Ensure that the specified display is valid for presentations. + // This check will normally disallow the default display unless it was + // configured as a presentation display for some reason. + if (mPresentationDisplayId >= 0) { + for (Display display : displays) { + if (display.getDisplayId() == mPresentationDisplayId) { + return display; + } + } + return null; + } + + // Find the indicated Wifi display by its address. + if (mDeviceAddress != null) { + for (Display display : displays) { + if (display.getType() == Display.TYPE_WIFI + && mDeviceAddress.equals(display.getAddress())) { + return display; + } + } + return null; + } + + // For the default route, choose the first presentation display from the list. + if (this == sStatic.mDefaultAudioVideo && displays.length > 0) { + return displays[0]; + } + } + return null; + } + + /** @hide */ + public String getDeviceAddress() { + return mDeviceAddress; + } + /** * Returns true if this route is enabled and may be selected. * @@ -1418,7 +1910,22 @@ public class MediaRouter { * @return True if this route is in the process of connecting. */ public boolean isConnecting() { - return mStatusCode == STATUS_CONNECTING; + return mResolvedStatusCode == STATUS_CONNECTING; + } + + /** @hide */ + public boolean isSelected() { + return this == sStatic.mSelectedRoute; + } + + /** @hide */ + public boolean isDefault() { + return this == sStatic.mDefaultAudioVideo; + } + + /** @hide */ + public void select() { + selectRouteStatic(mSupportedTypes, this, true); } void setStatusInt(CharSequence status) { @@ -1432,6 +1939,7 @@ public class MediaRouter { } final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() { + @Override public void dispatchRemoteVolumeUpdate(final int direction, final int value) { sStatic.mHandler.post(new Runnable() { @Override @@ -1460,7 +1968,7 @@ public class MediaRouter { ", status=" + getStatus() + ", category=" + getCategory() + ", supportedTypes=" + supportedTypes + - ", presentationDisplay=" + mPresentationDisplay + "}"; + ", presentationDisplay=" + mPresentationDisplay + " }"; } } @@ -1716,6 +2224,7 @@ public class MediaRouter { mVolumeHandling = PLAYBACK_VOLUME_FIXED; } + @Override CharSequence getName(Resources res) { if (mUpdateName) updateName(); return super.getName(res); @@ -1916,7 +2425,7 @@ public class MediaRouter { final int count = mRoutes.size(); if (count == 0) { // Don't keep empty groups in the router. - MediaRouter.removeRoute(this); + MediaRouter.removeRouteStatic(this); return; } @@ -2071,6 +2580,7 @@ public class MediaRouter { return mIsSystem; } + @Override public String toString() { return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) + " groupable=" + mGroupable + " }"; @@ -2091,8 +2601,12 @@ public class MediaRouter { } public boolean filterRouteEvent(RouteInfo route) { + return filterRouteEvent(route.mSupportedTypes); + } + + public boolean filterRouteEvent(int supportedTypes) { return (flags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0 - || (type & route.mSupportedTypes) != 0; + || (type & supportedTypes) != 0; } } diff --git a/core/res/res/values-mcc302-mnc510/config.xml b/media/java/android/media/MediaRouterClientState.aidl index 706570c0bd6b..7007711980ad 100644 --- a/core/res/res/values-mcc302-mnc510/config.xml +++ b/media/java/android/media/MediaRouterClientState.aidl @@ -1,7 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 2013, The Android Open Source Project +/* Copyright 2013, 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. @@ -15,11 +12,7 @@ ** See the License for the specific language governing permissions and ** limitations under the License. */ ---> -<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Don't use roaming icon for considered operators --> - <string-array translatable="false" name="config_operatorConsideredNonRoaming"> - <item>302</item> - </string-array> -</resources> +package android.media; + +parcelable MediaRouterClientState; diff --git a/media/java/android/media/MediaRouterClientState.java b/media/java/android/media/MediaRouterClientState.java new file mode 100644 index 000000000000..54b827631b5b --- /dev/null +++ b/media/java/android/media/MediaRouterClientState.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; + +/** + * Information available from MediaRouterService about the state perceived by + * a particular client and the routes that are available to it. + * + * Clients must not modify the contents of this object. + * @hide + */ +public final class MediaRouterClientState implements Parcelable { + /** + * A list of all known routes. + */ + public final ArrayList<RouteInfo> routes; + + /** + * The id of the current globally selected route, or null if none. + * Globally selected routes override any other route selections that applications + * may have made. Used for remote displays. + */ + public String globallySelectedRouteId; + + public MediaRouterClientState() { + routes = new ArrayList<RouteInfo>(); + } + + MediaRouterClientState(Parcel src) { + routes = src.createTypedArrayList(RouteInfo.CREATOR); + globallySelectedRouteId = src.readString(); + } + + public RouteInfo getRoute(String id) { + final int count = routes.size(); + for (int i = 0; i < count; i++) { + final RouteInfo route = routes.get(i); + if (route.id.equals(id)) { + return route; + } + } + return null; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(routes); + dest.writeString(globallySelectedRouteId); + } + + @Override + public String toString() { + return "MediaRouterClientState{ globallySelectedRouteId=" + + globallySelectedRouteId + ", routes=" + routes.toString() + " }"; + } + + public static final Parcelable.Creator<MediaRouterClientState> CREATOR = + new Parcelable.Creator<MediaRouterClientState>() { + @Override + public MediaRouterClientState createFromParcel(Parcel in) { + return new MediaRouterClientState(in); + } + + @Override + public MediaRouterClientState[] newArray(int size) { + return new MediaRouterClientState[size]; + } + }; + + public static final class RouteInfo implements Parcelable { + public String id; + public String name; + public String description; + public int supportedTypes; + public boolean enabled; + public int statusCode; + public int playbackType; + public int playbackStream; + public int volume; + public int volumeMax; + public int volumeHandling; + public int presentationDisplayId; + + public RouteInfo(String id) { + this.id = id; + enabled = true; + statusCode = MediaRouter.RouteInfo.STATUS_NONE; + playbackType = MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE; + playbackStream = -1; + volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; + presentationDisplayId = -1; + } + + public RouteInfo(RouteInfo other) { + id = other.id; + name = other.name; + description = other.description; + supportedTypes = other.supportedTypes; + enabled = other.enabled; + statusCode = other.statusCode; + playbackType = other.playbackType; + playbackStream = other.playbackStream; + volume = other.volume; + volumeMax = other.volumeMax; + volumeHandling = other.volumeHandling; + presentationDisplayId = other.presentationDisplayId; + } + + RouteInfo(Parcel in) { + id = in.readString(); + name = in.readString(); + description = in.readString(); + supportedTypes = in.readInt(); + enabled = in.readInt() != 0; + statusCode = in.readInt(); + playbackType = in.readInt(); + playbackStream = in.readInt(); + volume = in.readInt(); + volumeMax = in.readInt(); + volumeHandling = in.readInt(); + presentationDisplayId = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(name); + dest.writeString(description); + dest.writeInt(supportedTypes); + dest.writeInt(enabled ? 1 : 0); + dest.writeInt(statusCode); + dest.writeInt(playbackType); + dest.writeInt(playbackStream); + dest.writeInt(volume); + dest.writeInt(volumeMax); + dest.writeInt(volumeHandling); + dest.writeInt(presentationDisplayId); + } + + @Override + public String toString() { + return "RouteInfo{ id=" + id + + ", name=" + name + + ", description=" + description + + ", supportedTypes=0x" + Integer.toHexString(supportedTypes) + + ", enabled=" + enabled + + ", statusCode=" + statusCode + + ", playbackType=" + playbackType + + ", playbackStream=" + playbackStream + + ", volume=" + volume + + ", volumeMax=" + volumeMax + + ", volumeHandling=" + volumeHandling + + ", presentationDisplayId=" + presentationDisplayId + + " }"; + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator<RouteInfo> CREATOR = + new Parcelable.Creator<RouteInfo>() { + @Override + public RouteInfo createFromParcel(Parcel in) { + return new RouteInfo(in); + } + + @Override + public RouteInfo[] newArray(int size) { + return new RouteInfo[size]; + } + }; + } +} diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java index 6dbb3cd1da61..910b24c20637 100644 --- a/media/java/android/media/RemoteController.java +++ b/media/java/android/media/RemoteController.java @@ -404,7 +404,7 @@ public final class RemoteController * @throws IllegalArgumentException */ public boolean setSynchronizationMode(int sync) throws IllegalArgumentException { - if ((sync != POSITION_SYNCHRONIZATION_NONE) || (sync != POSITION_SYNCHRONIZATION_CHECK)) { + if ((sync != POSITION_SYNCHRONIZATION_NONE) && (sync != POSITION_SYNCHRONIZATION_CHECK)) { throw new IllegalArgumentException("Unknown synchronization mode " + sync); } if (!mIsRegistered) { diff --git a/media/java/android/media/RemoteDisplayState.aidl b/media/java/android/media/RemoteDisplayState.aidl new file mode 100644 index 000000000000..b3262fc51393 --- /dev/null +++ b/media/java/android/media/RemoteDisplayState.aidl @@ -0,0 +1,18 @@ +/* Copyright 2013, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.media; + +parcelable RemoteDisplayState; diff --git a/media/java/android/media/RemoteDisplayState.java b/media/java/android/media/RemoteDisplayState.java new file mode 100644 index 000000000000..1197f65925e2 --- /dev/null +++ b/media/java/android/media/RemoteDisplayState.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.ArrayList; + +/** + * Information available from IRemoteDisplayProvider about available remote displays. + * + * Clients must not modify the contents of this object. + * @hide + */ +public final class RemoteDisplayState implements Parcelable { + // Note: These constants are used by the remote display provider API. + // Do not change them! + public static final String SERVICE_INTERFACE = + "com.android.media.remotedisplay.RemoteDisplayProvider"; + public static final int DISCOVERY_MODE_NONE = 0; + public static final int DISCOVERY_MODE_PASSIVE = 1; + public static final int DISCOVERY_MODE_ACTIVE = 2; + + /** + * A list of all remote displays. + */ + public final ArrayList<RemoteDisplayInfo> displays; + + public RemoteDisplayState() { + displays = new ArrayList<RemoteDisplayInfo>(); + } + + RemoteDisplayState(Parcel src) { + displays = src.createTypedArrayList(RemoteDisplayInfo.CREATOR); + } + + public boolean isValid() { + if (displays == null) { + return false; + } + final int count = displays.size(); + for (int i = 0; i < count; i++) { + if (!displays.get(i).isValid()) { + return false; + } + } + return true; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(displays); + } + + public static final Parcelable.Creator<RemoteDisplayState> CREATOR = + new Parcelable.Creator<RemoteDisplayState>() { + @Override + public RemoteDisplayState createFromParcel(Parcel in) { + return new RemoteDisplayState(in); + } + + @Override + public RemoteDisplayState[] newArray(int size) { + return new RemoteDisplayState[size]; + } + }; + + public static final class RemoteDisplayInfo implements Parcelable { + // Note: These constants are used by the remote display provider API. + // Do not change them! + public static final int STATUS_NOT_AVAILABLE = 0; + public static final int STATUS_IN_USE = 1; + public static final int STATUS_AVAILABLE = 2; + public static final int STATUS_CONNECTING = 3; + public static final int STATUS_CONNECTED = 4; + + public static final int PLAYBACK_VOLUME_VARIABLE = + MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; + public static final int PLAYBACK_VOLUME_FIXED = + MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; + + public String id; + public String name; + public String description; + public int status; + public int volume; + public int volumeMax; + public int volumeHandling; + public int presentationDisplayId; + + public RemoteDisplayInfo(String id) { + this.id = id; + status = STATUS_NOT_AVAILABLE; + volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; + presentationDisplayId = -1; + } + + public RemoteDisplayInfo(RemoteDisplayInfo other) { + id = other.id; + name = other.name; + description = other.description; + status = other.status; + volume = other.volume; + volumeMax = other.volumeMax; + volumeHandling = other.volumeHandling; + presentationDisplayId = other.presentationDisplayId; + } + + RemoteDisplayInfo(Parcel in) { + id = in.readString(); + name = in.readString(); + description = in.readString(); + status = in.readInt(); + volume = in.readInt(); + volumeMax = in.readInt(); + volumeHandling = in.readInt(); + presentationDisplayId = in.readInt(); + } + + public boolean isValid() { + return !TextUtils.isEmpty(id) && !TextUtils.isEmpty(name); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(name); + dest.writeString(description); + dest.writeInt(status); + dest.writeInt(volume); + dest.writeInt(volumeMax); + dest.writeInt(volumeHandling); + dest.writeInt(presentationDisplayId); + } + + @Override + public String toString() { + return "RemoteDisplayInfo{ id=" + id + + ", name=" + name + + ", description=" + description + + ", status=" + status + + ", volume=" + volume + + ", volumeMax=" + volumeMax + + ", volumeHandling=" + volumeHandling + + ", presentationDisplayId=" + presentationDisplayId + + " }"; + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator<RemoteDisplayInfo> CREATOR = + new Parcelable.Creator<RemoteDisplayInfo>() { + @Override + public RemoteDisplayInfo createFromParcel(Parcel in) { + return new RemoteDisplayInfo(in); + } + + @Override + public RemoteDisplayInfo[] newArray(int size) { + return new RemoteDisplayInfo[size]; + } + }; + } +} diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 63a61e2edf22..dea971e4cfbc 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -41,13 +41,13 @@ LOCAL_SHARED_LIBRARIES := \ libstagefright_amrnb_common \ LOCAL_REQUIRED_MODULES := \ - libexif_jni + libjhead_jni LOCAL_STATIC_LIBRARIES := \ libstagefright_amrnbenc LOCAL_C_INCLUDES += \ - external/jhead \ + external/libexif/ \ external/tremor/Tremor \ frameworks/base/core/jni \ frameworks/av/media/libmedia \ diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index d1346679c31a..4be9cd611ded 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -115,6 +115,7 @@ void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *o nativeParcel->setData(obj->data(), obj->dataSize()); env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, jParcel); + env->DeleteLocalRef(jParcel); } } else { env->CallStaticVoidMethod(mClass, fields.post_event, mObject, diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index 77c796652442..067e7a6af8b0 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -37,7 +37,10 @@ #include "mtp.h" extern "C" { -#include "jhead.h" +#include "libexif/exif-content.h" +#include "libexif/exif-data.h" +#include "libexif/exif-tag.h" +#include "libexif/exif-utils.h" } using namespace android; @@ -750,6 +753,22 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, return result; } +static void foreachentry(ExifEntry *entry, void *user) { + char buf[1024]; + ALOGI("entry %x, format %d, size %d: %s", + entry->tag, entry->format, entry->size, exif_entry_get_value(entry, buf, sizeof(buf))); +} + +static void foreachcontent(ExifContent *content, void *user) { + ALOGI("content %d", exif_content_get_ifd(content)); + exif_content_foreach_entry(content, foreachentry, user); +} + +static long getLongFromExifEntry(ExifEntry *e) { + ExifByteOrder o = exif_data_get_byte_order(e->parent->parent); + return exif_get_long(e->data, o); +} + MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info) { char date[20]; @@ -792,23 +811,22 @@ MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, // read EXIF data for thumbnail information if (info.mFormat == MTP_FORMAT_EXIF_JPEG || info.mFormat == 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) { - info.mThumbCompressedSize = ImageInfo.ThumbnailSize; - info.mThumbFormat = MTP_FORMAT_EXIF_JPEG; - info.mImagePixWidth = ImageInfo.Width; - info.mImagePixHeight = ImageInfo.Height; - } + + ExifData *exifdata = exif_data_new_from_file(path); + if (exifdata) { + //exif_data_foreach_content(exifdata, foreachcontent, NULL); + + // XXX get this from exif, or parse jpeg header instead? + ExifEntry *w = exif_content_get_entry( + exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_X_DIMENSION); + ExifEntry *h = exif_content_get_entry( + exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_Y_DIMENSION); + info.mThumbCompressedSize = exifdata->data ? exifdata->size : 0; + info.mThumbFormat = MTP_FORMAT_EXIF_JPEG; + info.mImagePixWidth = w ? getLongFromExifEntry(w) : 0; + info.mImagePixHeight = h ? getLongFromExifEntry(h) : 0; + exif_data_unref(exifdata); } - DiscardData(); } checkAndClearExceptionFromCallback(env, __FUNCTION__); @@ -824,22 +842,16 @@ void* MyMtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) 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); + + ExifData *exifdata = exif_data_new_from_file(path); + if (exifdata) { + if (exifdata->data) { + result = malloc(exifdata->size); + if (result) { + memcpy(result, exifdata->data, exifdata->size); + } } - DiscardData(); + exif_data_unref(exifdata); } } diff --git a/media/lib/Android.mk b/media/lib/Android.mk new file mode 100644 index 000000000000..50799a6b0780 --- /dev/null +++ b/media/lib/Android.mk @@ -0,0 +1,46 @@ +# +# Copyright (C) 2013 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. +# +LOCAL_PATH := $(call my-dir) + +# the library +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_MODULE:= com.android.media.remotedisplay +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := \ + $(call all-subdir-java-files) \ + $(call all-aidl-files-under, java) + +include $(BUILD_JAVA_LIBRARY) + + +# ==== com.android.media.remotedisplay.xml lib def ======================== +include $(CLEAR_VARS) + +LOCAL_MODULE := com.android.media.remotedisplay.xml +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE_CLASS := ETC + +# This will install the file in /system/etc/permissions +# +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions + +LOCAL_SRC_FILES := $(LOCAL_MODULE) + +include $(BUILD_PREBUILT) diff --git a/media/lib/README.txt b/media/lib/README.txt new file mode 100644 index 000000000000..cade3df95408 --- /dev/null +++ b/media/lib/README.txt @@ -0,0 +1,28 @@ +This library (com.android.media.remotedisplay.jar) is a shared java library +containing classes required by unbundled remote display providers. + +--- Rules of this library --- +o This library is effectively a PUBLIC API for unbundled remote display providers + that may be distributed outside the system image. So it MUST BE API STABLE. + You can add but not remove. The rules are the same as for the + public platform SDK API. +o This library can see and instantiate internal platform classes, but it must not + expose them in any public method (or by extending them via inheritance). This would + break clients of the library because they cannot see the internal platform classes. + +This library is distributed in the system image, and loaded as +a shared library. So you can change the implementation, but not +the interface. In this way it is like framework.jar. + +--- Why does this library exists? --- + +Unbundled remote display providers (such as Cast) cannot use internal +platform classes. + +This library will eventually be replaced when the media route provider +infrastructure that is currently defined in the support library is reintegrated +with the framework in a new API. That API isn't ready yet so this +library is a compromise to make new capabilities available to the system +without exposing the full surface area of the support library media +route provider protocol. + diff --git a/core/res/res/layout/media_route_list_item_top_header.xml b/media/lib/com.android.media.remotedisplay.xml index 0c49b24820a5..77a91d23e1d8 100644 --- a/core/res/res/layout/media_route_list_item_top_header.xml +++ b/media/lib/com.android.media.remotedisplay.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 The Android Open Source Project +<!-- Copyright (C) 2013 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,16 +14,7 @@ limitations under the License. --> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@android:id/text1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceSmall" - android:background="#19ffffff" - android:textStyle="bold" - android:textAllCaps="true" - android:gravity="center_vertical" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:minHeight="24dp" -/> +<permissions> + <library name="com.android.media.remotedisplay" + file="/system/framework/com.android.media.remotedisplay.jar" /> +</permissions> diff --git a/media/lib/java/com/android/media/remotedisplay/RemoteDisplay.java b/media/lib/java/com/android/media/remotedisplay/RemoteDisplay.java new file mode 100644 index 000000000000..5e15702355f6 --- /dev/null +++ b/media/lib/java/com/android/media/remotedisplay/RemoteDisplay.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.media.remotedisplay; + +import com.android.internal.util.Objects; + +import android.media.MediaRouter; +import android.media.RemoteDisplayState.RemoteDisplayInfo; +import android.text.TextUtils; + +/** + * Represents a remote display that has been discovered. + */ +public class RemoteDisplay { + private final RemoteDisplayInfo mMutableInfo; + private RemoteDisplayInfo mImmutableInfo; + + /** + * Status code: Indicates that the remote display is not available. + */ + public static final int STATUS_NOT_AVAILABLE = RemoteDisplayInfo.STATUS_NOT_AVAILABLE; + + /** + * Status code: Indicates that the remote display is in use by someone else. + */ + public static final int STATUS_IN_USE = RemoteDisplayInfo.STATUS_IN_USE; + + /** + * Status code: Indicates that the remote display is available for new connections. + */ + public static final int STATUS_AVAILABLE = RemoteDisplayInfo.STATUS_AVAILABLE; + + /** + * Status code: Indicates that the remote display is current connecting. + */ + public static final int STATUS_CONNECTING = RemoteDisplayInfo.STATUS_CONNECTING; + + /** + * Status code: Indicates that the remote display is connected and is mirroring + * display contents. + */ + public static final int STATUS_CONNECTED = RemoteDisplayInfo.STATUS_CONNECTED; + + /** + * Volume handling: Output volume can be changed. + */ + public static final int PLAYBACK_VOLUME_VARIABLE = + RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE; + + /** + * Volume handling: Output volume is fixed. + */ + public static final int PLAYBACK_VOLUME_FIXED = + RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED; + + /** + * Creates a remote display with the specified name and id. + */ + public RemoteDisplay(String id, String name) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id must not be null or empty"); + } + mMutableInfo = new RemoteDisplayInfo(id); + setName(name); + } + + public String getId() { + return mMutableInfo.id; + } + + public String getName() { + return mMutableInfo.name; + } + + public void setName(String name) { + if (!Objects.equal(mMutableInfo.name, name)) { + mMutableInfo.name = name; + mImmutableInfo = null; + } + } + + public String getDescription() { + return mMutableInfo.description; + } + + public void setDescription(String description) { + if (!Objects.equal(mMutableInfo.description, description)) { + mMutableInfo.description = description; + mImmutableInfo = null; + } + } + + public int getStatus() { + return mMutableInfo.status; + } + + public void setStatus(int status) { + if (mMutableInfo.status != status) { + mMutableInfo.status = status; + mImmutableInfo = null; + } + } + + public int getVolume() { + return mMutableInfo.volume; + } + + public void setVolume(int volume) { + if (mMutableInfo.volume != volume) { + mMutableInfo.volume = volume; + mImmutableInfo = null; + } + } + + public int getVolumeMax() { + return mMutableInfo.volumeMax; + } + + public void setVolumeMax(int volumeMax) { + if (mMutableInfo.volumeMax != volumeMax) { + mMutableInfo.volumeMax = volumeMax; + mImmutableInfo = null; + } + } + + public int getVolumeHandling() { + return mMutableInfo.volumeHandling; + } + + public void setVolumeHandling(int volumeHandling) { + if (mMutableInfo.volumeHandling != volumeHandling) { + mMutableInfo.volumeHandling = volumeHandling; + mImmutableInfo = null; + } + } + + public int getPresentationDisplayId() { + return mMutableInfo.presentationDisplayId; + } + + public void setPresentationDisplayId(int presentationDisplayId) { + if (mMutableInfo.presentationDisplayId != presentationDisplayId) { + mMutableInfo.presentationDisplayId = presentationDisplayId; + mImmutableInfo = null; + } + } + + @Override + public String toString() { + return "RemoteDisplay{" + mMutableInfo.toString() + "}"; + } + + RemoteDisplayInfo getInfo() { + if (mImmutableInfo == null) { + mImmutableInfo = new RemoteDisplayInfo(mMutableInfo); + } + return mImmutableInfo; + } +} diff --git a/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java new file mode 100644 index 000000000000..e2df77c711c0 --- /dev/null +++ b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.media.remotedisplay; + +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.media.IRemoteDisplayCallback; +import android.media.IRemoteDisplayProvider; +import android.media.RemoteDisplayState; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.provider.Settings; +import android.util.ArrayMap; + +import java.util.Collection; + +/** + * Base class for remote display providers implemented as unbundled services. + * <p> + * To implement your remote display provider service, create a subclass of + * {@link Service} and override the {@link Service#onBind Service.onBind()} method + * to return the provider's binder when the {@link #SERVICE_INTERFACE} is requested. + * </p> + * <pre> + * public class SampleRemoteDisplayProviderService extends Service { + * private SampleProvider mProvider; + * + * public IBinder onBind(Intent intent) { + * if (intent.getAction().equals(RemoteDisplayProvider.SERVICE_INTERFACE)) { + * if (mProvider == null) { + * mProvider = new SampleProvider(this); + * } + * return mProvider.getBinder(); + * } + * return null; + * } + * + * class SampleProvider extends RemoteDisplayProvider { + * public SampleProvider() { + * super(SampleRemoteDisplayProviderService.this); + * } + * + * // --- Implementation goes here --- + * } + * } + * </pre> + * <p> + * Declare your remote display provider service in your application manifest + * like this: + * </p> + * <pre> + * <application> + * <uses-library android:name="com.android.media.remotedisplay" /> + * + * <service android:name=".SampleRemoteDisplayProviderService" + * android:label="@string/sample_remote_display_provider_service" + * android:exported="true" + * android:permission="android.permission.BIND_REMOTE_DISPLAY"> + * <intent-filter> + * <action android:name="com.android.media.remotedisplay.RemoteDisplayProvider" /> + * </intent-filter> + * </service> + * </application> + * </pre> + * <p> + * This object is not thread safe. It is only intended to be accessed on the + * {@link Context#getMainLooper main looper thread} of an application. + * </p><p> + * IMPORTANT: This class is effectively a public API for unbundled applications, and + * must remain API stable. See README.txt in the root of this package for more information. + * </p> + */ +public abstract class RemoteDisplayProvider { + private static final int MSG_SET_CALLBACK = 1; + private static final int MSG_SET_DISCOVERY_MODE = 2; + private static final int MSG_CONNECT = 3; + private static final int MSG_DISCONNECT = 4; + private static final int MSG_SET_VOLUME = 5; + private static final int MSG_ADJUST_VOLUME = 6; + + private final Context mContext; + private final ProviderStub mStub; + private final ProviderHandler mHandler; + private final ArrayMap<String, RemoteDisplay> mDisplays = + new ArrayMap<String, RemoteDisplay>(); + private IRemoteDisplayCallback mCallback; + private int mDiscoveryMode = DISCOVERY_MODE_NONE; + + private PendingIntent mSettingsPendingIntent; + + /** + * The {@link Intent} that must be declared as handled by the service. + * Put this in your manifest. + */ + public static final String SERVICE_INTERFACE = RemoteDisplayState.SERVICE_INTERFACE; + + /** + * Discovery mode: Do not perform any discovery. + */ + public static final int DISCOVERY_MODE_NONE = RemoteDisplayState.DISCOVERY_MODE_NONE; + + /** + * Discovery mode: Passive or low-power periodic discovery. + * <p> + * This mode indicates that an application is interested in knowing whether there + * are any remote displays paired or available but doesn't need the latest or + * most detailed information. The provider may scan at a lower rate or rely on + * knowledge of previously paired devices. + * </p> + */ + public static final int DISCOVERY_MODE_PASSIVE = RemoteDisplayState.DISCOVERY_MODE_PASSIVE; + + /** + * Discovery mode: Active discovery. + * <p> + * This mode indicates that the user is actively trying to connect to a route + * and we should perform continuous scans. This mode may use significantly more + * power but is intended to be short-lived. + * </p> + */ + public static final int DISCOVERY_MODE_ACTIVE = RemoteDisplayState.DISCOVERY_MODE_ACTIVE; + + /** + * Creates a remote display provider. + * + * @param context The application context for the remote display provider. + */ + public RemoteDisplayProvider(Context context) { + mContext = context; + mStub = new ProviderStub(); + mHandler = new ProviderHandler(context.getMainLooper()); + } + + /** + * Gets the context of the remote display provider. + */ + public final Context getContext() { + return mContext; + } + + /** + * Gets the Binder associated with the provider. + * <p> + * This is intended to be used for the onBind() method of a service that implements + * a remote display provider service. + * </p> + * + * @return The IBinder instance associated with the provider. + */ + public IBinder getBinder() { + return mStub; + } + + /** + * Called when the current discovery mode changes. + * + * @param mode The new discovery mode. + */ + public void onDiscoveryModeChanged(int mode) { + } + + /** + * Called when the system would like to connect to a display. + * + * @param display The remote display. + */ + public void onConnect(RemoteDisplay display) { + } + + /** + * Called when the system would like to disconnect from a display. + * + * @param display The remote display. + */ + public void onDisconnect(RemoteDisplay display) { + } + + /** + * Called when the system would like to set the volume of a display. + * + * @param display The remote display. + * @param volume The desired volume. + */ + public void onSetVolume(RemoteDisplay display, int volume) { + } + + /** + * Called when the system would like to adjust the volume of a display. + * + * @param display The remote display. + * @param delta An increment to add to the current volume, such as +1 or -1. + */ + public void onAdjustVolume(RemoteDisplay display, int delta) { + } + + /** + * Gets the current discovery mode. + * + * @return The current discovery mode. + */ + public int getDiscoveryMode() { + return mDiscoveryMode; + } + + /** + * Gets the current collection of displays. + * + * @return The current collection of displays, which must not be modified. + */ + public Collection<RemoteDisplay> getDisplays() { + return mDisplays.values(); + } + + /** + * Adds the specified remote display and notifies the system. + * + * @param display The remote display that was added. + * @throws IllegalStateException if there is already a display with the same id. + */ + public void addDisplay(RemoteDisplay display) { + if (display == null || mDisplays.containsKey(display.getId())) { + throw new IllegalArgumentException("display"); + } + mDisplays.put(display.getId(), display); + publishState(); + } + + /** + * Updates information about the specified remote display and notifies the system. + * + * @param display The remote display that was added. + * @throws IllegalStateException if the display was n + */ + public void updateDisplay(RemoteDisplay display) { + if (display == null || mDisplays.get(display.getId()) != display) { + throw new IllegalArgumentException("display"); + } + publishState(); + } + + /** + * Removes the specified remote display and tells the system about it. + * + * @param display The remote display that was removed. + */ + public void removeDisplay(RemoteDisplay display) { + if (display == null || mDisplays.get(display.getId()) != display) { + throw new IllegalArgumentException("display"); + } + mDisplays.remove(display.getId()); + publishState(); + } + + /** + * Finds the remote display with the specified id, returns null if not found. + * + * @param id Id of the remote display. + * @return The display, or null if none. + */ + public RemoteDisplay findRemoteDisplay(String id) { + return mDisplays.get(id); + } + + /** + * Gets a pending intent to launch the remote display settings activity. + * + * @return A pending intent to launch the settings activity. + */ + public PendingIntent getSettingsPendingIntent() { + if (mSettingsPendingIntent == null) { + Intent settingsIntent = new Intent(Settings.ACTION_WIFI_DISPLAY_SETTINGS); + settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mSettingsPendingIntent = PendingIntent.getActivity( + mContext, 0, settingsIntent, 0, null); + } + return mSettingsPendingIntent; + } + + void setCallback(IRemoteDisplayCallback callback) { + mCallback = callback; + publishState(); + } + + void setDiscoveryMode(int mode) { + if (mDiscoveryMode != mode) { + mDiscoveryMode = mode; + onDiscoveryModeChanged(mode); + } + } + + void publishState() { + if (mCallback != null) { + RemoteDisplayState state = new RemoteDisplayState(); + final int count = mDisplays.size(); + for (int i = 0; i < count; i++) { + final RemoteDisplay display = mDisplays.valueAt(i); + state.displays.add(display.getInfo()); + } + try { + mCallback.onStateChanged(state); + } catch (RemoteException ex) { + // system server died? + } + } + } + + final class ProviderStub extends IRemoteDisplayProvider.Stub { + @Override + public void setCallback(IRemoteDisplayCallback callback) { + mHandler.obtainMessage(MSG_SET_CALLBACK, callback).sendToTarget(); + } + + @Override + public void setDiscoveryMode(int mode) { + mHandler.obtainMessage(MSG_SET_DISCOVERY_MODE, mode, 0).sendToTarget(); + } + + @Override + public void connect(String id) { + mHandler.obtainMessage(MSG_CONNECT, id).sendToTarget(); + } + + @Override + public void disconnect(String id) { + mHandler.obtainMessage(MSG_DISCONNECT, id).sendToTarget(); + } + + @Override + public void setVolume(String id, int volume) { + mHandler.obtainMessage(MSG_SET_VOLUME, volume, 0, id).sendToTarget(); + } + + @Override + public void adjustVolume(String id, int delta) { + mHandler.obtainMessage(MSG_ADJUST_VOLUME, delta, 0, id).sendToTarget(); + } + } + + final class ProviderHandler extends Handler { + public ProviderHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_CALLBACK: { + setCallback((IRemoteDisplayCallback)msg.obj); + break; + } + case MSG_SET_DISCOVERY_MODE: { + setDiscoveryMode(msg.arg1); + break; + } + case MSG_CONNECT: { + RemoteDisplay display = findRemoteDisplay((String)msg.obj); + if (display != null) { + onConnect(display); + } + break; + } + case MSG_DISCONNECT: { + RemoteDisplay display = findRemoteDisplay((String)msg.obj); + if (display != null) { + onDisconnect(display); + } + break; + } + case MSG_SET_VOLUME: { + RemoteDisplay display = findRemoteDisplay((String)msg.obj); + if (display != null) { + onSetVolume(display, msg.arg1); + } + break; + } + case MSG_ADJUST_VOLUME: { + RemoteDisplay display = findRemoteDisplay((String)msg.obj); + if (display != null) { + onAdjustVolume(display, msg.arg1); + } + break; + } + } + } + } +} diff --git a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp index 6424744a4331..53f04bc6079c 100644 --- a/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp +++ b/media/tests/omxjpegdecoder/omx_jpeg_decoder.cpp @@ -30,6 +30,7 @@ #include <media/stagefright/MetaData.h> #include <media/stagefright/OMXClient.h> #include <media/stagefright/OMXCodec.h> +#include <SkImage.h> #include <SkMallocPixelRef.h> #include "omx_jpeg_decoder.h" @@ -184,8 +185,7 @@ void OmxJpegImageDecoder::installPixelRef(MediaBuffer *buffer, sp<MediaSource> d void OmxJpegImageDecoder::configBitmapSize(SkBitmap* bm, SkBitmap::Config pref, int width, int height) { - bm->setConfig(getColorSpaceConfig(pref), width, height); - bm->setIsOpaque(true); + bm->setConfig(getColorSpaceConfig(pref), width, height, 0, kOpaque_SkAlphaType); } SkBitmap::Config OmxJpegImageDecoder::getColorSpaceConfig( diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java index 55d73f233608..1cbc221e1057 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java @@ -51,7 +51,7 @@ public class FilteringCursorWrapper extends AbstractCursor { mPosition = new int[count]; cursor.moveToPosition(-1); - while (cursor.moveToNext()) { + while (cursor.moveToNext() && mCount < count) { final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java index 3a8a3fbdd103..34ce42dc56f4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java @@ -55,6 +55,10 @@ import java.util.concurrent.TimeUnit; public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { private static final boolean LOGD = true; + // TODO: clean up cursor ownership so background thread doesn't traverse + // previously returned cursors for filtering/sorting; this currently races + // with the UI thread. + private static final int MAX_OUTSTANDING_RECENTS = 4; private static final int MAX_OUTSTANDING_RECENTS_SVELTE = 2; diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 5169fef5f4a2..edd62559417e 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -3,6 +3,7 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <application android:label="@string/app_label"> <provider diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml index 9e296e23fb2c..66d1e75f9b10 100644 --- a/packages/Keyguard/AndroidManifest.xml +++ b/packages/Keyguard/AndroidManifest.xml @@ -38,6 +38,7 @@ <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" /> <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> <application android:label="@string/app_name" android:process="com.android.systemui" diff --git a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml index 8be15cb4e30f..b4847f0e910c 100644 --- a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml +++ b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml @@ -32,12 +32,10 @@ android:id="@+id/carrier_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:singleLine="true" android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/kg_status_line_font_size" - android:textColor="?android:attr/textColorSecondary" - android:textAllCaps="@bool/kg_use_all_caps" /> + android:textColor="?android:attr/textColorSecondary" /> <LinearLayout android:layout_width="match_parent" diff --git a/packages/Keyguard/res/layout/keyguard_presentation.xml b/packages/Keyguard/res/layout/keyguard_presentation.xml new file mode 100644 index 000000000000..7df0b70332cf --- /dev/null +++ b/packages/Keyguard/res/layout/keyguard_presentation.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2013, 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. +*/ +--> + +<!-- This is a view that shows general status information in Keyguard. --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/res/com.android.keyguard" + android:id="@+id/presentation" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.android.keyguard.KeyguardStatusView + android:id="@+id/clock" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/keyguard_accessibility_status"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|top" + android:orientation="vertical" + android:focusable="true"> + <TextClock + android:id="@+id/clock_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|top" + android:textColor="@color/clock_white" + android:singleLine="true" + style="@style/widget_big_thin" + android:format12Hour="@string/keyguard_widget_12_hours_format" + android:format24Hour="@string/keyguard_widget_24_hours_format" + android:baselineAligned="true" + android:layout_marginBottom="@dimen/bottom_text_spacing_digital" /> + + <include layout="@layout/keyguard_status_area" /> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="10dip" + android:layout_gravity="center_horizontal" + android:src="@drawable/kg_security_lock_normal" /> + </LinearLayout> + </com.android.keyguard.KeyguardStatusView> + +</FrameLayout> diff --git a/packages/Keyguard/res/layout/keyguard_status_view.xml b/packages/Keyguard/res/layout/keyguard_status_view.xml index 5857fc24ea09..a4d298a043ad 100644 --- a/packages/Keyguard/res/layout/keyguard_status_view.xml +++ b/packages/Keyguard/res/layout/keyguard_status_view.xml @@ -26,7 +26,7 @@ android:layout_height="match_parent" androidprv:layout_maxWidth="@dimen/keyguard_security_width" androidprv:layout_maxHeight="@dimen/keyguard_security_height" - android:gravity="center_horizontal"> + android:gravity="center"> <com.android.keyguard.KeyguardStatusView android:id="@+id/keyguard_status_view_face_palm" diff --git a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java index 7d1f24f4963e..74e6f33668b0 100644 --- a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java +++ b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java @@ -19,7 +19,6 @@ package com.android.keyguard; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Color; -import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; @@ -41,7 +40,7 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli private static final String TAG = CameraWidgetFrame.class.getSimpleName(); private static final boolean DEBUG = KeyguardHostView.DEBUG; private static final int WIDGET_ANIMATION_DURATION = 250; // ms - private static final int WIDGET_WAIT_DURATION = 650; // ms + private static final int WIDGET_WAIT_DURATION = 400; // ms private static final int RECOVERY_DELAY = 1000; // ms interface Callbacks { @@ -68,6 +67,7 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli private FixedSizeFrameLayout mPreview; private View mFullscreenPreview; private View mFakeNavBar; + private boolean mUseFastTransition; private final Runnable mTransitionToCameraRunnable = new Runnable() { @Override @@ -243,11 +243,12 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0; final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0; - mPreview.setPivotX(0); + final boolean isRtl = mPreview.getLayoutDirection() == LAYOUT_DIRECTION_RTL; + mPreview.setPivotX(isRtl ? mPreview.width : 0); mPreview.setPivotY(0); mPreview.setScaleX(pvScale); mPreview.setScaleY(pvScale); - mPreview.setTranslationX(pvTransX); + mPreview.setTranslationX((isRtl ? -1 : 1) * pvTransX); mPreview.setTranslationY(pvTransY); mRenderedSize.set(width, height); @@ -417,7 +418,8 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli private void rescheduleTransitionToCamera() { if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis()); mHandler.removeCallbacks(mTransitionToCameraRunnable); - mHandler.postDelayed(mTransitionToCameraRunnable, WIDGET_WAIT_DURATION); + final long duration = mUseFastTransition ? 0 : WIDGET_WAIT_DURATION; + mHandler.postDelayed(mTransitionToCameraRunnable, duration); } private void cancelTransitionToCamera() { @@ -512,4 +514,8 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli if (DEBUG) Log.d(TAG, "setInsets: " + insets); mInsets.set(insets); } + + public void setUseFastTransition(boolean useFastTransition) { + mUseFastTransition = useFastTransition; + } } diff --git a/packages/Keyguard/src/com/android/keyguard/CarrierText.java b/packages/Keyguard/src/com/android/keyguard/CarrierText.java index c33f17477b46..88558cd420bf 100644 --- a/packages/Keyguard/src/com/android/keyguard/CarrierText.java +++ b/packages/Keyguard/src/com/android/keyguard/CarrierText.java @@ -17,14 +17,18 @@ package com.android.keyguard; import android.content.Context; +import android.text.method.SingleLineTransformationMethod; import android.text.TextUtils; import android.util.AttributeSet; +import android.view.View; import android.widget.TextView; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.widget.LockPatternUtils; +import java.util.Locale; + public class CarrierText extends TextView { private static CharSequence mSeparator; @@ -77,6 +81,8 @@ public class CarrierText extends TextView { public CarrierText(Context context, AttributeSet attrs) { super(context, attrs); mLockPatternUtils = new LockPatternUtils(mContext); + boolean useAllCaps = mContext.getResources().getBoolean(R.bool.kg_use_all_caps); + setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps)); } protected void updateCarrierText(State simState, CharSequence plmn, CharSequence spn) { @@ -258,4 +264,25 @@ public class CarrierText extends TextView { return mContext.getText(carrierHelpTextId); } + + private class CarrierTextTransformationMethod extends SingleLineTransformationMethod { + private final Locale mLocale; + private final boolean mAllCaps; + + public CarrierTextTransformationMethod(Context context, boolean allCaps) { + mLocale = context.getResources().getConfiguration().locale; + mAllCaps = allCaps; + } + + @Override + public CharSequence getTransformation(CharSequence source, View view) { + source = super.getTransformation(source, view); + + if (mAllCaps && source != null) { + source = source.toString().toUpperCase(mLocale); + } + + return source; + } + } } diff --git a/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java index 677f1f19fdd4..2ee21acd14e2 100644 --- a/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java +++ b/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java @@ -39,7 +39,7 @@ public interface ChallengeLayout { * * @param b true to show, false to hide */ - void showChallenge(boolean b); + void showChallenge(boolean show); /** * Show the bouncer challenge. This may block access to other child views. diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java b/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java index 9a1aa5b79564..0a915eaf688e 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java @@ -99,6 +99,11 @@ public abstract class KeyguardActivityLauncher { public void launchCamera(Handler worker, Runnable onSecureCameraStarted) { LockPatternUtils lockPatternUtils = getLockPatternUtils(); + + // Workaround to avoid camera release/acquisition race when resuming face unlock + // after showing lockscreen camera (bug 11063890). + KeyguardUpdateMonitor.getInstance(getContext()).setAlternateUnlockEnabled(false); + if (lockPatternUtils.isSecure()) { // Launch the secure version of the camera if (wouldLaunchResolverActivity(SECURE_CAMERA_INTENT)) { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java new file mode 100644 index 000000000000..6bcbd6c5e8ba --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard; + +import android.app.Presentation; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnDismissListener; +import android.graphics.Point; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; +import android.os.Bundle; +import android.util.Slog; +import android.view.Display; +import android.view.View; +import android.view.WindowManager; + +public class KeyguardDisplayManager { + protected static final String TAG = "KeyguardDisplayManager"; + private static boolean DEBUG = KeyguardViewMediator.DEBUG; + Presentation mPresentation; + private MediaRouter mMediaRouter; + private Context mContext; + private boolean mShowing; + + KeyguardDisplayManager(Context context) { + mContext = context; + mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); + } + + void show() { + if (!mShowing) { + if (DEBUG) Slog.v(TAG, "show"); + mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); + updateDisplays(true); + } + mShowing = true; + } + + void hide() { + if (mShowing) { + if (DEBUG) Slog.v(TAG, "hide"); + mMediaRouter.removeCallback(mMediaRouterCallback); + updateDisplays(false); + } + mShowing = false; + } + + private final MediaRouter.SimpleCallback mMediaRouterCallback = + new MediaRouter.SimpleCallback() { + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + if (DEBUG) Slog.d(TAG, "onRouteSelected: type=" + type + ", info=" + info); + updateDisplays(mShowing); + } + + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + if (DEBUG) Slog.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info); + updateDisplays(mShowing); + } + + @Override + public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { + if (DEBUG) Slog.d(TAG, "onRoutePresentationDisplayChanged: info=" + info); + updateDisplays(mShowing); + } + }; + + private OnDismissListener mOnDismissListener = new OnDismissListener() { + + @Override + public void onDismiss(DialogInterface dialog) { + mPresentation = null; + } + }; + + protected void updateDisplays(boolean showing) { + if (showing) { + MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute( + MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY); + boolean useDisplay = route != null + && route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE; + Display presentationDisplay = useDisplay ? route.getPresentationDisplay() : null; + + if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) { + if (DEBUG) Slog.v(TAG, "Display gone: " + mPresentation.getDisplay()); + mPresentation.dismiss(); + mPresentation = null; + } + + if (mPresentation == null && presentationDisplay != null) { + if (DEBUG) Slog.i(TAG, "Keyguard enabled on display: " + presentationDisplay); + mPresentation = new KeyguardPresentation(mContext, presentationDisplay); + mPresentation.setOnDismissListener(mOnDismissListener); + try { + mPresentation.show(); + } catch (WindowManager.InvalidDisplayException ex) { + Slog.w(TAG, "Invalid display:", ex); + mPresentation = null; + } + } + } else { + if (mPresentation != null) { + mPresentation.dismiss(); + mPresentation = null; + } + } + } + + private final static class KeyguardPresentation extends Presentation { + private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height + private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s + private View mClock; + private int mUsableWidth; + private int mUsableHeight; + private int mMarginTop; + private int mMarginLeft; + Runnable mMoveTextRunnable = new Runnable() { + @Override + public void run() { + int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth())); + int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight())); + mClock.setTranslationX(x); + mClock.setTranslationY(y); + mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT); + } + }; + + public KeyguardPresentation(Context context, Display display) { + super(context, display); + getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + + public void onDetachedFromWindow() { + mClock.removeCallbacks(mMoveTextRunnable); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Point p = new Point(); + getDisplay().getSize(p); + mUsableWidth = VIDEO_SAFE_REGION * p.x/100; + mUsableHeight = VIDEO_SAFE_REGION * p.y/100; + mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200; + mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200; + + setContentView(R.layout.keyguard_presentation); + mClock = findViewById(R.id.clock); + + // Avoid screen burn in + mClock.post(mMoveTextRunnable); + } + } +} diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java index fdc06a64556a..247089f5bad1 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java @@ -218,7 +218,7 @@ public class KeyguardHostView extends KeyguardViewBase { mTransportState = (dcs.clearing ? TRANSPORT_GONE : (isMusicPlaying(dcs.playbackState) ? TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE)); - if (DEBUG) Log.v(TAG, "Initial transport state: " + if (DEBUGXPORT) Log.v(TAG, "Initial transport state: " + mTransportState + ", pbstate=" + dcs.playbackState); } @@ -1369,7 +1369,7 @@ public class KeyguardHostView extends KeyguardViewBase { } } - Runnable mSwitchPageRunnable = new Runnable() { + private final Runnable mSwitchPageRunnable = new Runnable() { @Override public void run() { showAppropriateWidgetPage(); @@ -1438,7 +1438,7 @@ public class KeyguardHostView extends KeyguardViewBase { mAppWidgetToShow = ss.appWidgetToShow; setInsets(ss.insets); if (DEBUG) Log.d(TAG, "onRestoreInstanceState, transport=" + mTransportState); - post(mSwitchPageRunnable); + mSwitchPageRunnable.run(); } @Override @@ -1472,10 +1472,21 @@ public class KeyguardHostView extends KeyguardViewBase { } private void showAppropriateWidgetPage() { - int state = mTransportState; - ensureTransportPresentOrRemoved(state); - int pageToShow = getAppropriateWidgetPage(state); - mAppWidgetContainer.setCurrentPage(pageToShow); + final int state = mTransportState; + final boolean transportAdded = ensureTransportPresentOrRemoved(state); + final int pageToShow = getAppropriateWidgetPage(state); + if (!transportAdded) { + mAppWidgetContainer.setCurrentPage(pageToShow); + } else if (state == TRANSPORT_VISIBLE) { + // If the transport was just added, we need to wait for layout to happen before + // we can set the current page. + post(new Runnable() { + @Override + public void run() { + mAppWidgetContainer.setCurrentPage(pageToShow); + } + }); + } } /** @@ -1499,12 +1510,11 @@ public class KeyguardHostView extends KeyguardViewBase { * * @param state */ - private void ensureTransportPresentOrRemoved(int state) { + private boolean ensureTransportPresentOrRemoved(int state) { final boolean showing = getWidgetPosition(R.id.keyguard_transport_control) != -1; final boolean visible = state == TRANSPORT_VISIBLE; final boolean shouldBeVisible = state == TRANSPORT_INVISIBLE && isMusicPlaying(state); if (!showing && (visible || shouldBeVisible)) { - if (DEBUGXPORT) Log.v(TAG, "add transport"); // insert to left of camera if it exists, otherwise after right-most widget int lastWidget = mAppWidgetContainer.getChildCount() - 1; int position = 0; // handle no widget case @@ -1512,13 +1522,16 @@ public class KeyguardHostView extends KeyguardViewBase { position = mAppWidgetContainer.isCameraPage(lastWidget) ? lastWidget : lastWidget + 1; } + if (DEBUGXPORT) Log.v(TAG, "add transport at " + position); mAppWidgetContainer.addWidget(getOrCreateTransportControl(), position); + return true; } else if (showing && state == TRANSPORT_GONE) { if (DEBUGXPORT) Log.v(TAG, "remove transport"); mAppWidgetContainer.removeWidget(getOrCreateTransportControl()); mTransportControl = null; KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(null); } + return false; } private CameraWidgetFrame findCameraPage() { @@ -1642,6 +1655,7 @@ public class KeyguardHostView extends KeyguardViewBase { KeyguardWidgetFrame frame = mAppWidgetContainer.getWidgetPageAt(i); frame.removeAllViews(); } + mSecurityViewContainer.onPause(); // clean up any actions in progress } /** diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java index 69075ec3d03a..751572c72761 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java @@ -177,6 +177,7 @@ class KeyguardMessageArea extends TextView { public KeyguardMessageArea(Context context, AttributeSet attrs) { super(context, attrs); + setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug mLockPatternUtils = new LockPatternUtils(context); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java index e7f12599e7ab..3b72f4327e3b 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java @@ -58,8 +58,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit // how many cells the user has to cross before we poke the wakelock private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2; - private int mFailedPatternAttemptsSinceLastTimeout = 0; - private int mTotalFailedPatternAttempts = 0; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private CountDownTimer mCountdownTimer = null; private LockPatternUtils mLockPatternUtils; private LockPatternView mLockPatternView; @@ -100,6 +100,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit public KeyguardPatternView(Context context, AttributeSet attrs) { super(context, attrs); + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); } public void setKeyguardCallback(KeyguardSecurityCallback callback) { @@ -202,7 +203,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit if (mCallback.isVerifyUnlockOnly()) { updateFooter(FooterMode.VerifyUnlocked); } else if (mEnableFallback && - (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { + (mKeyguardUpdateMonitor.getFailedUnlockAttempts() + >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { updateFooter(FooterMode.ForgotLockPattern); } else { updateFooter(FooterMode.Normal); @@ -211,7 +213,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit } private void displayDefaultSecurityMessage() { - if (KeyguardUpdateMonitor.getInstance(mContext).getMaxBiometricUnlockAttemptsReached()) { + if (mKeyguardUpdateMonitor.getMaxBiometricUnlockAttemptsReached()) { mSecurityMessageDisplay.setMessage(R.string.faceunlock_multiple_failures, true); } else { mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false); @@ -262,20 +264,20 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit if (mLockPatternUtils.checkPattern(pattern)) { mCallback.reportSuccessfulUnlockAttempt(); mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); - mTotalFailedPatternAttempts = 0; mCallback.dismiss(true); } else { if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS); } mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); - if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { - mTotalFailedPatternAttempts++; - mFailedPatternAttemptsSinceLastTimeout++; + boolean registeredAttempt = + pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL; + if (registeredAttempt) { mCallback.reportFailedUnlockAttempt(); } - if (mFailedPatternAttemptsSinceLastTimeout - >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { + int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts(); + if (registeredAttempt && + 0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); handleAttemptLockout(deadline); } else { @@ -363,7 +365,6 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit mLockPatternView.setEnabled(true); displayDefaultSecurityMessage(); // TODO mUnlockIcon.setVisibility(View.VISIBLE); - mFailedPatternAttemptsSinceLastTimeout = 0; if (mEnableFallback) { updateFooter(FooterMode.ForgotLockPattern); } else { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java index 8ccd6fe1915b..36b2446c6b68 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java @@ -68,8 +68,6 @@ public class KeyguardService extends Service { } private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() { - private boolean mSetHiddenCalled; - private boolean mIsHidden; public boolean isShowing() { return mKeyguardViewMediator.isShowing(); } @@ -91,10 +89,7 @@ public class KeyguardService extends Service { } public void setHidden(boolean isHidden) { checkPermission(); - if (mSetHiddenCalled && mIsHidden == isHidden) return; mKeyguardViewMediator.setHidden(isHidden); - mSetHiddenCalled = true; - mIsHidden = isHidden; } public void dismiss() { mKeyguardViewMediator.dismiss(); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java index e39622a54c66..9accbb44cd77 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java @@ -46,9 +46,10 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { private static final String LOG_TAG = "KeyguardSimPinView"; private static final boolean DEBUG = KeyguardViewMediator.DEBUG; + public static final String TAG = "KeyguardSimPinView"; private ProgressDialog mSimUnlockProgressDialog = null; - private volatile boolean mSimCheckInProgress; + private CheckSimPin mCheckSimPinThread; private AlertDialog mRemainingAttemptsDialog; @@ -169,14 +170,17 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView @Override public void run() { try { + Log.v(TAG, "call supplyPinReportResult()"); final int[] result = ITelephony.Stub.asInterface(ServiceManager .checkService("phone")).supplyPinReportResult(mPin); + Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]); post(new Runnable() { public void run() { onSimCheckResponse(result[0], result[1]); } }); } catch (RemoteException e) { + Log.e(TAG, "RemoteException for supplyPinReportResult:", e); post(new Runnable() { public void run() { onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); @@ -229,9 +233,8 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView getSimUnlockProgressDialog().show(); - if (!mSimCheckInProgress) { - mSimCheckInProgress = true; // there should be only one - new CheckSimPin(mPasswordEntry.getText().toString()) { + if (mCheckSimPinThread == null) { + mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText().toString()) { void onSimCheckResponse(final int result, final int attemptsRemaining) { post(new Runnable() { public void run() { @@ -263,11 +266,12 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView mPasswordEntry.setText(""); } mCallback.userActivity(0); - mSimCheckInProgress = false; + mCheckSimPinThread = null; } }); } - }.start(); + }; + mCheckSimPinThread.start(); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java index 31518a1e31f8..6e9e83e8cc2f 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java @@ -45,9 +45,10 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { private static final String LOG_TAG = "KeyguardSimPukView"; private static final boolean DEBUG = KeyguardViewMediator.DEBUG; + public static final String TAG = "KeyguardSimPukView"; private ProgressDialog mSimUnlockProgressDialog = null; - private volatile boolean mCheckInProgress; + private CheckSimPuk mCheckSimPukThread; private String mPukText; private String mPinText; private StateMachine mStateMachine = new StateMachine(); @@ -220,15 +221,17 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView @Override public void run() { try { + Log.v(TAG, "call supplyPukReportResult()"); final int[] result = ITelephony.Stub.asInterface(ServiceManager .checkService("phone")).supplyPukReportResult(mPuk, mPin); - + Log.v(TAG, "supplyPukReportResult returned: " + result[0] + " " + result[1]); post(new Runnable() { public void run() { onSimLockChangedResponse(result[0], result[1]); } }); } catch (RemoteException e) { + Log.e(TAG, "RemoteException for supplyPukReportResult:", e); post(new Runnable() { public void run() { onSimLockChangedResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); @@ -295,9 +298,8 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView private void updateSim() { getSimUnlockProgressDialog().show(); - if (!mCheckInProgress) { - mCheckInProgress = true; - new CheckSimPuk(mPukText, mPinText) { + if (mCheckSimPukThread == null) { + mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText) { void onSimLockChangedResponse(final int result, final int attemptsRemaining) { post(new Runnable() { public void run() { @@ -326,11 +328,12 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView + " attemptsRemaining=" + attemptsRemaining); mStateMachine.reset(); } - mCheckInProgress = false; + mCheckSimPukThread = null; } }); } - }.start(); + }; + mCheckSimPukThread.start(); } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java index d93327526f65..0bfee38cc84c 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java @@ -98,26 +98,13 @@ public class KeyguardStatusView extends GridLayout { } protected void refresh() { - Resources res = mContext.getResources(); - Locale locale = Locale.getDefault(); - final String dateFormat = DateFormat.getBestDateTimePattern(locale, - res.getString(R.string.abbrev_wday_month_day_no_year)); - - mDateView.setFormat24Hour(dateFormat); - mDateView.setFormat12Hour(dateFormat); - - // 12-hour clock. - // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton - // format. The following code removes the AM/PM indicator if we didn't want it. - final String clock12skel = res.getString(R.string.clock_12hr_format); - String clock12hr = DateFormat.getBestDateTimePattern(locale, clock12skel); - clock12hr = clock12skel.contains("a") ? clock12hr : clock12hr.replaceAll("a", "").trim(); - mClockView.setFormat12Hour(clock12hr); - - // 24-hour clock - final String clock24skel = res.getString(R.string.clock_24hr_format); - final String clock24hr = DateFormat.getBestDateTimePattern(locale, clock24skel); - mClockView.setFormat24Hour(clock24hr); + Patterns.update(mContext); + + mDateView.setFormat24Hour(Patterns.dateView); + mDateView.setFormat12Hour(Patterns.dateView); + + mClockView.setFormat12Hour(Patterns.clockView12); + mClockView.setFormat24Hour(Patterns.clockView24); refreshAlarmStatus(); } @@ -149,4 +136,35 @@ public class KeyguardStatusView extends GridLayout { return LockPatternUtils.ID_DEFAULT_STATUS_WIDGET; } + // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. + // This is an optimization to ensure we only recompute the patterns when the inputs change. + private static final class Patterns { + static String dateView; + static String clockView12; + static String clockView24; + static String cacheKey; + + static void update(Context context) { + final Locale locale = Locale.getDefault(); + final Resources res = context.getResources(); + final String dateViewSkel = res.getString(R.string.abbrev_wday_month_day_no_year); + final String clockView12Skel = res.getString(R.string.clock_12hr_format); + final String clockView24Skel = res.getString(R.string.clock_24hr_format); + final String key = locale.toString() + dateViewSkel + clockView12Skel + clockView24Skel; + if (key.equals(cacheKey)) return; + + dateView = DateFormat.getBestDateTimePattern(locale, dateViewSkel); + + clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel); + // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton + // format. The following code removes the AM/PM indicator if we didn't want it. + if (!clockView12Skel.contains("a")) { + clockView12 = clockView12.replaceAll("a", "").trim(); + } + + clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel); + + cacheKey = key; + } + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java index 29ba60d102c9..349078f2c6f8 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java @@ -60,12 +60,12 @@ import java.util.TimeZone; */ public class KeyguardTransportControlView extends FrameLayout { - private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s private static final int RESET_TO_METADATA_DELAY = 5000; protected static final boolean DEBUG = false; protected static final String TAG = "TransportControlView"; private static final boolean ANIMATE_TRANSITIONS = true; + protected static final long QUIESCENT_PLAYBACK_FACTOR = 1000; private ViewGroup mMetadataContainer; private ViewGroup mInfoContainer; @@ -89,11 +89,9 @@ public class KeyguardTransportControlView extends FrameLayout { private ImageView mBadge; private boolean mSeekEnabled; - private boolean mUserSeeking; private java.text.DateFormat mFormat; - private Date mTimeElapsed; - private Date mTrackDuration; + private Date mTempDate = new Date(); /** * The metadata which should be populated into the view once we've been attached @@ -104,23 +102,32 @@ public class KeyguardTransportControlView extends FrameLayout { new RemoteController.OnClientUpdateListener() { @Override public void onClientChange(boolean clearing) { - clearMetadata(); + if (clearing) { + clearMetadata(); + } } @Override public void onClientPlaybackStateUpdate(int state) { - setSeekBarsEnabled(false); updatePlayPauseState(state); } @Override public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) { - setSeekBarsEnabled(mMetadata != null && mMetadata.duration > 0); updatePlayPauseState(state); if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state + ", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs + ", speed=" + speed + ")"); + + removeCallbacks(mUpdateSeekBars); + // Since the music client may be responding to historical events that cause the + // playback state to change dramatically, wait until things become quiescent before + // resuming automatic scrub position update. + if (mTransientSeek.getVisibility() == View.VISIBLE + && playbackPositionShouldMove(mCurrentPlayState)) { + postDelayed(mUpdateSeekBars, QUIESCENT_PLAYBACK_FACTOR); + } } @Override @@ -134,15 +141,21 @@ public class KeyguardTransportControlView extends FrameLayout { } }; - private final Runnable mUpdateSeekBars = new Runnable() { + private class UpdateSeekBarRunnable implements Runnable { public void run() { - if (updateSeekBars()) { + boolean seekAble = updateOnce(); + if (seekAble) { removeCallbacks(this); postDelayed(this, 1000); } } + public boolean updateOnce() { + return updateSeekBars(); + } }; + private final UpdateSeekBarRunnable mUpdateSeekBars = new UpdateSeekBarRunnable(); + private final Runnable mResetToMetadata = new Runnable() { public void run() { resetToMetadata(); @@ -161,6 +174,7 @@ public class KeyguardTransportControlView extends FrameLayout { } if (keyCode != -1) { sendMediaButtonClick(keyCode); + delayResetToMetadata(); // if the scrub bar is showing, keep showing it. } } }; @@ -175,25 +189,67 @@ public class KeyguardTransportControlView extends FrameLayout { } }; + // This class is here to throttle scrub position updates to the music client + class FutureSeekRunnable implements Runnable { + private int mProgress; + private boolean mPending; + + public void run() { + scrubTo(mProgress); + mPending = false; + } + + void setProgress(int progress) { + mProgress = progress; + if (!mPending) { + mPending = true; + postDelayed(this, 30); + } + } + }; + + // This is here because RemoteControlClient's method isn't visible :/ + private final static boolean playbackPositionShouldMove(int playstate) { + switch(playstate) { + case RemoteControlClient.PLAYSTATE_STOPPED: + case RemoteControlClient.PLAYSTATE_PAUSED: + case RemoteControlClient.PLAYSTATE_BUFFERING: + case RemoteControlClient.PLAYSTATE_ERROR: + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + return false; + case RemoteControlClient.PLAYSTATE_PLAYING: + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + case RemoteControlClient.PLAYSTATE_REWINDING: + default: + return true; + } + } + + private final FutureSeekRunnable mFutureSeekRunnable = new FutureSeekRunnable(); + private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { - scrubTo(progress); + mFutureSeekRunnable.setProgress(progress); delayResetToMetadata(); + mTempDate.setTime(progress); + mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate)); + } else { + updateSeekDisplay(); } - updateSeekDisplay(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { - mUserSeeking = true; + delayResetToMetadata(); + removeCallbacks(mUpdateSeekBars); // don't update during user interaction } @Override public void onStopTrackingTouch(SeekBar seekBar) { - mUserSeeking = false; } }; @@ -206,10 +262,10 @@ public class KeyguardTransportControlView extends FrameLayout { = new KeyguardUpdateMonitorCallback() { public void onScreenTurnedOff(int why) { setEnableMarquee(false); - }; + } public void onScreenTurnedOn() { setEnableMarquee(true); - }; + } }; public KeyguardTransportControlView(Context context, AttributeSet attrs) { @@ -245,17 +301,11 @@ public class KeyguardTransportControlView extends FrameLayout { if (enabled == mSeekEnabled) return; mSeekEnabled = enabled; - if (mTransientSeek.getVisibility() == VISIBLE) { + if (mTransientSeek.getVisibility() == VISIBLE && !enabled) { mTransientSeek.setVisibility(INVISIBLE); mMetadataContainer.setVisibility(VISIBLE); - mUserSeeking = false; cancelResetToMetadata(); } - if (enabled) { - mUpdateSeekBars.run(); - } else { - removeCallbacks(mUpdateSeekBars); - } } public void setTransportControlCallback(KeyguardHostView.TransportControlCallback @@ -292,6 +342,8 @@ public class KeyguardTransportControlView extends FrameLayout { } final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); setEnableMarquee(screenOn); + // Allow long-press anywhere else in this view to show the seek bar + setOnLongClickListener(mTransportShowSeekBarListener); } @Override @@ -324,10 +376,36 @@ public class KeyguardTransportControlView extends FrameLayout { mAudioManager.unregisterRemoteController(mRemoteController); KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor); mMetadata.clear(); - mUserSeeking = false; removeCallbacks(mUpdateSeekBars); } + @Override + protected Parcelable onSaveInstanceState() { + SavedState ss = new SavedState(super.onSaveInstanceState()); + ss.artist = mMetadata.artist; + ss.trackTitle = mMetadata.trackTitle; + ss.albumTitle = mMetadata.albumTitle; + ss.duration = mMetadata.duration; + ss.bitmap = mMetadata.bitmap; + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + mMetadata.artist = ss.artist; + mMetadata.trackTitle = ss.trackTitle; + mMetadata.albumTitle = ss.albumTitle; + mMetadata.duration = ss.duration; + mMetadata.bitmap = ss.bitmap; + populateMetadata(); + } + void setBadgeIcon(Drawable bmp) { mBadge.setImageDrawable(bmp); @@ -455,18 +533,12 @@ public class KeyguardTransportControlView extends FrameLayout { void updateSeekDisplay() { if (mMetadata != null && mRemoteController != null && mFormat != null) { - if (mTimeElapsed == null) { - mTimeElapsed = new Date(); - } - if (mTrackDuration == null) { - mTrackDuration = new Date(); - } - mTimeElapsed.setTime(mRemoteController.getEstimatedMediaPosition()); - mTrackDuration.setTime(mMetadata.duration); - mTransientSeekTimeElapsed.setText(mFormat.format(mTimeElapsed)); - mTransientSeekTimeTotal.setText(mFormat.format(mTrackDuration)); + mTempDate.setTime(mRemoteController.getEstimatedMediaPosition()); + mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate)); + mTempDate.setTime(mMetadata.duration); + mTransientSeekTimeTotal.setText(mFormat.format(mTempDate)); - if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTimeElapsed + + if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTempDate + " duration=" + mMetadata.duration); } } @@ -479,10 +551,16 @@ public class KeyguardTransportControlView extends FrameLayout { mTransientSeek.setVisibility(INVISIBLE); mMetadataContainer.setVisibility(VISIBLE); cancelResetToMetadata(); + removeCallbacks(mUpdateSeekBars); // don't update if scrubber isn't visible } else { mTransientSeek.setVisibility(VISIBLE); mMetadataContainer.setVisibility(INVISIBLE); delayResetToMetadata(); + if (playbackPositionShouldMove(mCurrentPlayState)) { + mUpdateSeekBars.run(); + } else { + mUpdateSeekBars.updateOnce(); + } } mTransportControlCallback.userActivity(); return true; @@ -544,9 +622,6 @@ public class KeyguardTransportControlView extends FrameLayout { case RemoteControlClient.PLAYSTATE_PLAYING: imageResId = R.drawable.ic_media_pause; imageDescId = R.string.keyguard_transport_pause_description; - if (mSeekEnabled) { - mUpdateSeekBars.run(); - } break; case RemoteControlClient.PLAYSTATE_BUFFERING: @@ -561,10 +636,9 @@ public class KeyguardTransportControlView extends FrameLayout { break; } - if (state != RemoteControlClient.PLAYSTATE_PLAYING) { - removeCallbacks(mUpdateSeekBars); - updateSeekBars(); - } + boolean clientSupportsSeek = mMetadata != null && mMetadata.duration > 0; + setSeekBarsEnabled(clientSupportsSeek); + mBtnPlay.setImageResource(imageResId); mBtnPlay.setContentDescription(getResources().getString(imageDescId)); mCurrentPlayState = state; @@ -572,11 +646,9 @@ public class KeyguardTransportControlView extends FrameLayout { boolean updateSeekBars() { final int position = (int) mRemoteController.getEstimatedMediaPosition(); + if (DEBUG) Log.v(TAG, "Estimated time:" + position); if (position >= 0) { - if (DEBUG) Log.v(TAG, "Seek to " + position); - if (!mUserSeeking) { - mTransientSeekBar.setProgress(position); - } + mTransientSeekBar.setProgress(position); return true; } Log.w(TAG, "Updating seek bars; received invalid estimated media position (" + @@ -587,6 +659,11 @@ public class KeyguardTransportControlView extends FrameLayout { static class SavedState extends BaseSavedState { boolean clientPresent; + String artist; + String trackTitle; + String albumTitle; + long duration; + Bitmap bitmap; SavedState(Parcelable superState) { super(superState); @@ -594,13 +671,23 @@ public class KeyguardTransportControlView extends FrameLayout { private SavedState(Parcel in) { super(in); - this.clientPresent = in.readInt() != 0; + clientPresent = in.readInt() != 0; + artist = in.readString(); + trackTitle = in.readString(); + albumTitle = in.readString(); + duration = in.readLong(); + bitmap = Bitmap.CREATOR.createFromParcel(in); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); - out.writeInt(this.clientPresent ? 1 : 0); + out.writeInt(clientPresent ? 1 : 0); + out.writeString(artist); + out.writeString(trackTitle); + out.writeString(albumTitle); + out.writeLong(duration); + bitmap.writeToParcel(out, flags); } public static final Parcelable.Creator<SavedState> CREATOR @@ -627,34 +714,4 @@ public class KeyguardTransportControlView extends FrameLayout { public boolean providesClock() { return false; } - - private boolean wasPlayingRecently(int state, long stateChangeTimeMs) { - switch (state) { - case RemoteControlClient.PLAYSTATE_PLAYING: - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - case RemoteControlClient.PLAYSTATE_REWINDING: - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - case RemoteControlClient.PLAYSTATE_BUFFERING: - // actively playing or about to play - return true; - case RemoteControlClient.PLAYSTATE_NONE: - return false; - case RemoteControlClient.PLAYSTATE_STOPPED: - case RemoteControlClient.PLAYSTATE_PAUSED: - case RemoteControlClient.PLAYSTATE_ERROR: - // we have stopped playing, check how long ago - if (DEBUG) { - if ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS) { - Log.v(TAG, "wasPlayingRecently: time < TIMEOUT was playing recently"); - } else { - Log.v(TAG, "wasPlayingRecently: time > TIMEOUT"); - } - } - return ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS); - default: - Log.e(TAG, "Unknown playback state " + state + " in wasPlayingRecently()"); - return false; - } - } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java index 520cea32b013..a8493166ac84 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -815,7 +815,7 @@ public class KeyguardUpdateMonitor { for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onKeyguardVisibilityChanged(isShowing); + cb.onKeyguardVisibilityChangedRaw(isShowing); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 76f963733c98..c08880dfc872 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -19,6 +19,7 @@ import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.graphics.Bitmap; import android.media.AudioManager; +import android.os.SystemClock; import android.view.WindowManagerPolicy; import com.android.internal.telephony.IccCardConstants; @@ -27,6 +28,11 @@ import com.android.internal.telephony.IccCardConstants; * Callback for general information relevant to lock screen. */ class KeyguardUpdateMonitorCallback { + + private static final long VISIBILITY_CHANGED_COLLAPSE_MS = 1000; + private long mVisibilityChangedCalled; + private boolean mShowing; + /** * Called when the battery status changes, e.g. when plugged in or unplugged, charge * level, etc. changes. @@ -70,6 +76,15 @@ class KeyguardUpdateMonitorCallback { */ void onKeyguardVisibilityChanged(boolean showing) { } + void onKeyguardVisibilityChangedRaw(boolean showing) { + final long now = SystemClock.elapsedRealtime(); + if (showing == mShowing + && (now - mVisibilityChangedCalled) < VISIBILITY_CHANGED_COLLAPSE_MS) return; + onKeyguardVisibilityChanged(showing); + mVisibilityChangedCalled = now; + mShowing = showing; + } + /** * Called when visibility of lockscreen clock changes, such as when * obscured by a widget. diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java index fd7cae66f006..6aa0a4bfd1c7 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java @@ -61,9 +61,11 @@ import android.widget.FrameLayout; public class KeyguardViewManager { private final static boolean DEBUG = KeyguardViewMediator.DEBUG; private static String TAG = "KeyguardViewManager"; - public static boolean USE_UPPER_CASE = true; public final static String IS_SWITCHING_USER = "is_switching_user"; + // Delay dismissing keyguard to allow animations to complete. + private static final int HIDE_KEYGUARD_DELAY = 500; + // Timeout used for keypresses static final int DIGIT_PRESS_WAKE_MILLIS = 5000; @@ -509,9 +511,10 @@ public class KeyguardViewManager { mKeyguardHost.setCustomBackground(null); updateShowWallpaper(true); mKeyguardHost.removeView(lastView); + mViewMediatorCallback.keyguardGone(); } } - }, 500); + }, HIDE_KEYGUARD_DELAY); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java index b92ae905b028..4086f84271a3 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java @@ -200,7 +200,7 @@ public class KeyguardViewMediator { // cached value of whether we are showing (need to know this to quickly // answer whether the input should be restricted) - private boolean mShowing = false; + private boolean mShowing; // true if the keyguard is hidden by another window private boolean mHidden = false; @@ -253,6 +253,11 @@ public class KeyguardViewMediator { private final float mLockSoundVolume; /** + * For managing external displays + */ + private KeyguardDisplayManager mKeyguardDisplayManager; + + /** * Cache of avatar drawables, for use by KeyguardMultiUserAvatar. */ private static MultiUserAvatarCache sMultiUserAvatarCache = new MultiUserAvatarCache(); @@ -304,6 +309,11 @@ public class KeyguardViewMediator { * Report that the keyguard is dismissable, pending the next keyguardDone call. */ void keyguardDonePending(); + + /** + * Report when keyguard is actually gone + */ + void keyguardGone(); } KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @@ -457,6 +467,11 @@ public class KeyguardViewMediator { public void keyguardDonePending() { mKeyguardDonePending = true; } + + @Override + public void keyguardGone() { + mKeyguardDisplayManager.hide(); + } }; private void userActivity() { @@ -483,6 +498,8 @@ public class KeyguardViewMediator { mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION)); + mKeyguardDisplayManager = new KeyguardDisplayManager(context); + mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); @@ -491,6 +508,10 @@ public class KeyguardViewMediator { ? lockPatternUtils : new LockPatternUtils(mContext); mLockPatternUtils.setCurrentUser(UserHandle.USER_OWNER); + // Assume keyguard is showing (unless it's disabled) until we know for sure... + mShowing = (mUpdateMonitor.isDeviceProvisioned() || mLockPatternUtils.isSecure()) + && !mLockPatternUtils.isLockScreenDisabled(); + WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback, @@ -1218,6 +1239,7 @@ public class KeyguardViewMediator { mShowKeyguardWakeLock.release(); } + mKeyguardDisplayManager.show(); } /** diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java index 8e396286c934..169899f372c6 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java @@ -15,6 +15,9 @@ */ package com.android.keyguard; +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -46,6 +49,20 @@ public class KeyguardViewStateManager implements int mChallengeTop = 0; + private final AnimatorListener mPauseListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + mKeyguardSecurityContainer.onPause(); + } + }; + + private final AnimatorListener mResumeListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + if (((View)mKeyguardSecurityContainer).isShown()) { + mKeyguardSecurityContainer.onResume(0); + } + } + }; + public KeyguardViewStateManager(KeyguardHostView hostView) { mKeyguardHostView = hostView; } @@ -102,20 +119,20 @@ public class KeyguardViewStateManager implements } public void fadeOutSecurity(int duration) { - ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration).start(); + ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration) + .setListener(mPauseListener); } public void fadeInSecurity(int duration) { - ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration).start(); + ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration) + .setListener(mResumeListener); } public void onPageBeginMoving() { if (mChallengeLayout.isChallengeOverlapping() && mChallengeLayout instanceof SlidingChallengeLayout) { SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout; - if (!mKeyguardWidgetPager.isWarping()) { - scl.fadeOutChallenge(); - } + scl.fadeOutChallenge(); mPageIndexOnPageBeginMoving = mKeyguardWidgetPager.getCurrentPage(); } // We use mAppWidgetToShow to show a particular widget after you add it-- @@ -137,11 +154,12 @@ public class KeyguardViewStateManager implements public void onPageSwitching(View newPage, int newPageIndex) { if (mKeyguardWidgetPager != null && mChallengeLayout instanceof SlidingChallengeLayout) { boolean isCameraPage = newPage instanceof CameraWidgetFrame; - SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout; - scl.setChallengeInteractive(!isCameraPage); if (isCameraPage) { - scl.fadeOutChallenge(); + CameraWidgetFrame camera = (CameraWidgetFrame) newPage; + camera.setUseFastTransition(mKeyguardWidgetPager.isWarping()); } + SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout; + scl.setChallengeInteractive(!isCameraPage); final int currentFlags = mKeyguardWidgetPager.getSystemUiVisibility(); final int newFlags = isCameraPage ? (currentFlags | View.STATUS_BAR_DISABLE_SEARCH) : (currentFlags & ~View.STATUS_BAR_DISABLE_SEARCH); @@ -178,7 +196,7 @@ public class KeyguardViewStateManager implements boolean challengeOverlapping = mChallengeLayout.isChallengeOverlapping(); if (challengeOverlapping && !newCurPage.isSmall() && mPageListeningToSlider != newPageIndex) { - newCurPage.shrinkWidget(); + newCurPage.shrinkWidget(true); } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java index ab8a759c8112..8ee9b616d0c4 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java @@ -375,10 +375,6 @@ public class KeyguardWidgetFrame extends FrameLayout { return mSmallFrameHeight; } - public void shrinkWidget() { - shrinkWidget(true); - } - public void setWidgetLockedSmall(boolean locked) { if (locked) { setWidgetHeight(mSmallWidgetHeight); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java index 704af6e3aa39..99f775787cec 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java @@ -194,7 +194,9 @@ public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwit @Override public void onPageEndWarp() { - hideOutlinesAndSidePages(); + // if we're moving to the warp page, then immediately hide the other widgets. + int duration = getPageWarpIndex() == getNextPage() ? 0 : -1; + animateOutlinesAndSidePages(false, duration); mViewStateManager.onPageEndWarp(); } @@ -669,7 +671,7 @@ public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwit // On the very first measure pass, if the challenge is showing, we need to make sure // that the widget on the current page is small. if (challengeShowing && i == mCurrentPage && !mHasMeasure) { - frame.shrinkWidget(); + frame.shrinkWidget(true); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/PagedView.java b/packages/Keyguard/src/com/android/keyguard/PagedView.java index 9d237dc7c0ff..53c17a5e7adc 100644 --- a/packages/Keyguard/src/com/android/keyguard/PagedView.java +++ b/packages/Keyguard/src/com/android/keyguard/PagedView.java @@ -82,13 +82,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; // The page is moved more than halfway, automatically move to the next page on touch up. - private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; + private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.5f; // The following constants need to be scaled based on density. The scaled versions will be // assigned to the corresponding member variables below. - private static final int FLING_THRESHOLD_VELOCITY = 500; + private static final int FLING_THRESHOLD_VELOCITY = 1500; private static final int MIN_SNAP_VELOCITY = 1500; - private static final int MIN_FLING_VELOCITY = 250; + private static final int MIN_FLING_VELOCITY = 500; // We are disabling touch interaction of the widget region for factory ROM. private static final boolean DISABLE_TOUCH_INTERACTION = false; @@ -267,6 +267,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private boolean mIsCameraEvent; private float mWarpPeekAmount; + private boolean mOnPageEndWarpCalled; + private boolean mOnPageBeginWarpCalled; public interface PageSwitchListener { void onPageSwitching(View newPage, int newPageIndex); @@ -491,7 +493,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (!mIsPageMoving) { mIsPageMoving = true; if (isWarping()) { - onPageBeginWarp(); + dispatchOnPageBeginWarp(); if (mPageSwapIndex != -1) { swapPages(mPageSwapIndex, mPageWarpIndex); } @@ -500,6 +502,22 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } + private void dispatchOnPageBeginWarp() { + if (!mOnPageBeginWarpCalled) { + onPageBeginWarp(); + mOnPageBeginWarpCalled = true; + } + mOnPageEndWarpCalled = false; + } + + private void dispatchOnPageEndWarp() { + if (!mOnPageEndWarpCalled) { + onPageEndWarp(); + mOnPageEndWarpCalled = true; + } + mOnPageBeginWarpCalled = false; + } + protected void pageEndMoving() { if (DEBUG_WARP) Log.v(TAG, "pageEndMoving(" + mIsPageMoving + ")"); if (mIsPageMoving) { @@ -508,7 +526,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (mPageSwapIndex != -1) { swapPages(mPageSwapIndex, mPageWarpIndex); } - onPageEndWarp(); + dispatchOnPageEndWarp(); resetPageWarp(); } onPageEndMoving(); @@ -1919,11 +1937,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } if (isWarping()) { - onPageEndWarp(); + dispatchOnPageEndWarp(); + notifyPageSwitching(whichPage); resetPageWarp(); + } else { + notifyPageSwitching(whichPage); } - notifyPageSwitching(whichPage); View focusedChild = getFocusedChild(); if (focusedChild != null && whichPage != mCurrentPage && focusedChild == getPageAt(mCurrentPage)) { @@ -2260,11 +2280,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTempVisiblePagesRange[0] = 0; mTempVisiblePagesRange[1] = getPageCount() - 1; boundByReorderablePages(true, mTempVisiblePagesRange); - mReorderingStarted = true; // Check if we are within the reordering range if (mTempVisiblePagesRange[0] <= dragViewIndex && dragViewIndex <= mTempVisiblePagesRange[1]) { + mReorderingStarted = true; if (zoomOut()) { // Find the drag view under the pointer mDragView = getChildAt(dragViewIndex); @@ -2702,12 +2722,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override public void onAnimationEnd(Animator animation) { mWarpAnimation = null; - mWarpPageExposed = true; + mWarpPageExposed = false; } }; private void cancelWarpAnimation(String msg, boolean abortAnimation) { - if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ")"); + if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ",abort=" + abortAnimation + ")"); if (abortAnimation) { // We're done with the animation and moving to a new page. Let the scroller // take over the animation. @@ -2727,9 +2747,9 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private void animateWarpPageOnScreen(String reason) { if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOnScreen(" + reason + ")"); - if (isWarping()) { + if (isWarping() && !mWarpPageExposed) { mWarpPageExposed = true; - onPageBeginWarp(); + dispatchOnPageBeginWarp(); KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex); if (DEBUG_WARP) Log.v(TAG, "moving page on screen: Tx=" + v.getTranslationX()); DecelerateInterpolator interp = new DecelerateInterpolator(1.5f); @@ -2744,7 +2764,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private void animateWarpPageOffScreen(String reason, boolean animate) { if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOffScreen(" + reason + " anim:" + animate + ")"); if (isWarping()) { - onPageEndWarp(); + dispatchOnPageEndWarp(); KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex); if (DEBUG_WARP) Log.v(TAG, "moving page off screen: Tx=" + v.getTranslationX()); AccelerateInterpolator interp = new AccelerateInterpolator(1.5f); diff --git a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java index 7a9a1c877dc1..3d515cedba6f 100644 --- a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java +++ b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java @@ -1003,6 +1003,16 @@ public class SlidingChallengeLayout extends ViewGroup implements ChallengeLayout } } + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + // Focus security fileds before widgets. + if (mChallengeView != null && + mChallengeView.requestFocus(direction, previouslyFocusedRect)) { + return true; + } + return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); + } + public void computeScroll() { super.computeScroll(); diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml index f573d9dc3bbb..d9f0a9a13c3a 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml @@ -35,7 +35,6 @@ android:layout_marginBottom="32dip" android:layout_gravity="center" style="?android:attr/buttonBarButtonStyle" - android:singleLine="true" android:ellipsize="end" android:text="@string/print_error_default_message" android:textColor="@color/important_text" diff --git a/packages/PrintSpooler/res/values-ja/arrays.xml b/packages/PrintSpooler/res/values-ja/arrays.xml index 57088c84296e..460bdb214fb9 100644 --- a/packages/PrintSpooler/res/values-ja/arrays.xml +++ b/packages/PrintSpooler/res/values-ja/arrays.xml @@ -20,13 +20,13 @@ <item>JIS_B9</item> <item>JIS_B8</item> <item>JIS_B7</item> - <item>JIS_b6</item> - <item>JIS_b5</item> - <item>JIS_b4</item> - <item>JIS_b3</item> - <item>JIS_b2</item> - <item>JIS_b1</item> - <item>JIS_b0</item> + <item>JIS_B6</item> + <item>JIS_B5</item> + <item>JIS_B4</item> + <item>JIS_B3</item> + <item>JIS_B2</item> + <item>JIS_B1</item> + <item>JIS_B0</item> <item>JIS_EXEC</item> <item>JPN_CHOU4</item> <item>JPN_CHOU3</item> diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index f2e768a299f9..d2613d0f7266 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -93,6 +93,12 @@ <!-- Title of the action bar button to got to add a printer. [CHAR LIMIT=25] --> <string name="print_add_printer">Add printer</string> + <!-- Title of the menu item to select a printer. [CHAR LIMIT=25] --> + <string name="print_select_printer">Select printer</string> + + <!-- Title of the menu item to forget a printer. [CHAR LIMIT=25] --> + <string name="print_forget_printer">Forget printer</string> + <!-- Utterance to announce a change in the number of matches during a search. This is spoken to a blind user. [CHAR LIMIT=none] --> <plurals name="print_search_result_count_utterance"> <item quantity="one"><xliff:g id="count" example="1">%1$s</xliff:g> printer found</item> diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java index 060146750a55..9831839a3865 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java +++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java @@ -79,6 +79,8 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { private PrinterId mTrackedPrinter; + private boolean mPrintersUpdatedBefore; + public FusedPrintersProvider(Context context) { super(context); mPersistenceManager = new PersistenceManager(context); @@ -88,13 +90,14 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mPersistenceManager.addPrinterAndWritePrinterHistory(printer); } - private void computeAndDeliverResult(Map<PrinterId, PrinterInfo> discoveredPrinters) { + private void computeAndDeliverResult(ArrayMap<PrinterId, PrinterInfo> discoveredPrinters, + ArrayMap<PrinterId, PrinterInfo> favoritePrinters) { List<PrinterInfo> printers = new ArrayList<PrinterInfo>(); // Add the updated favorite printers. - final int favoritePrinterCount = mFavoritePrinters.size(); + final int favoritePrinterCount = favoritePrinters.size(); for (int i = 0; i < favoritePrinterCount; i++) { - PrinterInfo favoritePrinter = mFavoritePrinters.get(i); + PrinterInfo favoritePrinter = favoritePrinters.valueAt(i); PrinterInfo updatedPrinter = discoveredPrinters.remove( favoritePrinter.getId()); if (updatedPrinter != null) { @@ -123,8 +126,11 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mPrinters.addAll(printers); if (isStarted()) { - // Deliver the printers. + // If stated deliver the new printers. deliverResult(printers); + } else { + // Otherwise, take a note for the change. + onContentChanged(); } } @@ -165,6 +171,8 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { .getSystemService(Context.PRINT_SERVICE); mDiscoverySession = printManager.createPrinterDiscoverySession(); mPersistenceManager.readPrinterHistory(); + } else if (mPersistenceManager.isHistoryChanged()) { + mPersistenceManager.readPrinterHistory(); } if (mPersistenceManager.isReadHistoryCompleted() && !mDiscoverySession.isPrinterDiscoveryStarted()) { @@ -176,7 +184,7 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { + mDiscoverySession.getPrinters().size() + " " + FusedPrintersProvider.this.hashCode()); } - updatePrinters(mDiscoverySession.getPrinters()); + updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters); } }); final int favoriteCount = mFavoritePrinters.size(); @@ -187,15 +195,19 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mDiscoverySession.startPrinterDisovery(printerIds); List<PrinterInfo> printers = mDiscoverySession.getPrinters(); if (!printers.isEmpty()) { - updatePrinters(printers); + updatePrinters(printers, mFavoritePrinters); } } } - private void updatePrinters(List<PrinterInfo> printers) { - if (mPrinters.equals(printers)) { + private void updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters) { + if (mPrintersUpdatedBefore && mPrinters.equals(printers) + && mFavoritePrinters.equals(favoritePrinters)) { return; } + + mPrintersUpdatedBefore = true; + ArrayMap<PrinterId, PrinterInfo> printersMap = new ArrayMap<PrinterId, PrinterInfo>(); final int printerCount = printers.size(); @@ -203,7 +215,16 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { PrinterInfo printer = printers.get(i); printersMap.put(printer.getId(), printer); } - computeAndDeliverResult(printersMap); + + ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap = + new ArrayMap<PrinterId, PrinterInfo>(); + final int favoritePrinterCount = favoritePrinters.size(); + for (int i = 0; i < favoritePrinterCount; i++) { + PrinterInfo favoritePrinter = favoritePrinters.get(i); + favoritePrintersMap.put(favoritePrinter.getId(), favoritePrinter); + } + + computeAndDeliverResult(printersMap, favoritePrintersMap); } @Override @@ -264,6 +285,42 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { } } + public boolean isFavoritePrinter(PrinterId printerId) { + final int printerCount = mFavoritePrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo favoritePritner = mFavoritePrinters.get(i); + if (favoritePritner.getId().equals(printerId)) { + return true; + } + } + return false; + } + + public void forgetFavoritePrinter(PrinterId printerId) { + List<PrinterInfo> newFavoritePrinters = null; + + // Remove the printer from the favorites. + final int favoritePrinterCount = mFavoritePrinters.size(); + for (int i = 0; i < favoritePrinterCount; i++) { + PrinterInfo favoritePrinter = mFavoritePrinters.get(i); + if (favoritePrinter.getId().equals(printerId)) { + newFavoritePrinters = new ArrayList<PrinterInfo>(); + newFavoritePrinters.addAll(mPrinters); + newFavoritePrinters.remove(i); + break; + } + } + + // If we removed a favorite printer, we have work to do. + if (newFavoritePrinters != null) { + // Remove the printer from history and persist the latter. + mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId); + + // Recompute and deliver the printers. + updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters); + } + } + private final class PersistenceManager { private static final String PERSIST_FILE_NAME = "printer_history.xml"; @@ -281,13 +338,15 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { private final AtomicFile mStatePersistFile; - private List<PrinterInfo> mHistoricalPrinters; + private List<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>(); private boolean mReadHistoryCompleted; private boolean mReadHistoryInProgress; private ReadTask mReadTask; + private volatile long mLastReadHistoryTimestamp; + private PersistenceManager(Context context) { mStatePersistFile = new AtomicFile(new File(context.getFilesDir(), PERSIST_FILE_NAME)); @@ -327,6 +386,27 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { new ArrayList<PrinterInfo>(mHistoricalPrinters)); } + @SuppressWarnings("unchecked") + public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) { + boolean writeHistory = false; + final int printerCount = mHistoricalPrinters.size(); + for (int i = printerCount - 1; i >= 0; i--) { + PrinterInfo historicalPrinter = mHistoricalPrinters.get(i); + if (historicalPrinter.getId().equals(printerId)) { + mHistoricalPrinters.remove(i); + writeHistory = true; + } + } + if (writeHistory) { + new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, + new ArrayList<PrinterInfo>(mHistoricalPrinters)); + } + } + + public boolean isHistoryChanged() { + return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified(); + } + private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) { Map<PrinterId, PrinterRecord> recordMap = new ArrayMap<PrinterId, PrinterRecord>(); @@ -423,11 +503,10 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mReadHistoryInProgress = false; mReadHistoryCompleted = true; - // Deliver the favorites. - Map<PrinterId, PrinterInfo> discoveredPrinters = Collections.emptyMap(); - computeAndDeliverResult(discoveredPrinters); + // Deliver the printers. + updatePrinters(mDiscoverySession.getPrinters(), mHistoricalPrinters); - // Start loading the available printers. + // Loading the available printers if needed. loadInternal(); // We are done. @@ -450,6 +529,8 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); parseState(parser, printers); + // Take a note which version of the history was read. + mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified(); return printers; } catch (IllegalStateException ise) { Slog.w(LOG_TAG, "Failed parsing ", ise); diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java index 8f263612e5a0..f6008d4c5b6d 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java @@ -357,6 +357,9 @@ public class PrintJobConfigActivity extends Activity { } public void cancel() { + if (isWorking()) { + mRemotePrintAdapter.cancel(); + } mControllerState = CONTROLLER_STATE_CANCELLED; } @@ -934,6 +937,7 @@ public class PrintJobConfigActivity extends Activity { mPrintJobId, mCurrentPrinter); if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) { + mCapabilitiesTimeout.post(); updateUi(); return; } @@ -1525,9 +1529,13 @@ public class PrintJobConfigActivity extends Activity { builder.append(','); } PageRange pageRange = pageRanges[i]; - builder.append(pageRange.getStart()); - builder.append('-'); - builder.append(pageRange.getEnd()); + final int shownStartPage = pageRange.getStart() + 1; + final int shownEndPage = pageRange.getEnd() + 1; + builder.append(shownStartPage); + if (shownStartPage != shownEndPage) { + builder.append('-'); + builder.append(shownEndPage); + } } mPageRangeEditText.setText(builder.toString()); } @@ -2182,12 +2190,16 @@ public class PrintJobConfigActivity extends Activity { // Select the old color mode - nothing really changed. setColorModeSpinnerSelectionNoCallback(oldColorModeNewIndex); } else { - final int selectedColorModeIndex = Integer.numberOfTrailingZeros( - (colorModes & defaultAttributes.getColorMode())); - setColorModeSpinnerSelectionNoCallback(selectedColorModeIndex); - mCurrPrintAttributes.setColorMode(mColorModeSpinnerAdapter - .getItem(selectedColorModeIndex).value); - someAttributeSelectionChanged = true; + final int selectedColorMode = colorModes & defaultAttributes.getColorMode(); + final int itemCount = mColorModeSpinnerAdapter.getCount(); + for (int i = 0; i < itemCount; i++) { + SpinnerItem<Integer> item = mColorModeSpinnerAdapter.getItem(i); + if (selectedColorMode == item.value) { + setColorModeSpinnerSelectionNoCallback(i); + mCurrPrintAttributes.setColorMode(selectedColorMode); + someAttributeSelectionChanged = true; + } + } } } mColorModeSpinner.setEnabled(true); diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java index 609ae6406438..615d667cf17d 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java @@ -441,6 +441,7 @@ public final class PrintSpoolerService extends Service { private void removeObsoletePrintJobs() { synchronized (mLock) { + boolean persistState = false; final int printJobCount = mPrintJobs.size(); for (int i = printJobCount - 1; i >= 0; i--) { PrintJobInfo printJob = mPrintJobs.get(i); @@ -450,9 +451,12 @@ public final class PrintSpoolerService extends Service { Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString()); } removePrintJobFileLocked(printJob.getId()); + persistState = true; } } - mPersistanceManager.writeStateLocked(); + if (persistState) { + mPersistanceManager.writeStateLocked(); + } } } @@ -544,7 +548,7 @@ public final class PrintSpoolerService extends Service { final int printJobCount = mPrintJobs.size(); for (int i = 0; i < printJobCount; i++) { PrintJobInfo printJob = mPrintJobs.get(i); - if (isActiveState(printJob.getState()) + if (isActiveState(printJob.getState()) && printJob.getPrinterId() != null && printJob.getPrinterId().getServiceName().equals(service)) { return true; } @@ -799,6 +803,10 @@ public final class PrintSpoolerService extends Service { for (int j = 0; j < printJobCount; j++) { PrintJobInfo printJob = printJobs.get(j); + if (!shouldPersistPrintJob(printJob)) { + continue; + } + serializer.startTag(null, TAG_JOB); serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString()); diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java index fd14af929d1d..d9ccb5dad590 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java +++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java @@ -137,4 +137,15 @@ final class RemotePrintDocumentAdapter { Log.e(LOG_TAG, "Error calling finish()", re); } } + + public void cancel() { + if (DEBUG) { + Log.i(LOG_TAG, "cancel()"); + } + try { + mRemoteInterface.cancel(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling cancel()", re); + } + } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java index 204c15237a2d..fe5920cd0396 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java +++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java @@ -46,6 +46,8 @@ import android.printservice.PrintServiceInfo; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -54,6 +56,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.Filter; @@ -81,6 +84,8 @@ public final class SelectPrinterFragment extends Fragment { private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS = "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS"; + private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID"; + private final ArrayList<PrintServiceInfo> mAddPrinterServices = new ArrayList<PrintServiceInfo>(); @@ -127,6 +132,9 @@ public final class SelectPrinterFragment extends Fragment { mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) { + return; + } PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); Activity activity = getActivity(); if (activity instanceof OnPrinterSelectedListener) { @@ -138,6 +146,8 @@ public final class SelectPrinterFragment extends Fragment { } }); + registerForContextMenu(mListView); + return content; } @@ -185,6 +195,62 @@ public final class SelectPrinterFragment extends Fragment { } @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { + if (view == mListView) { + final int position = ((AdapterContextMenuInfo) menuInfo).position; + PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); + + menu.setHeaderTitle(printer.getName()); + + // Add the select menu item if applicable. + if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) { + MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer, + Menu.NONE, R.string.print_select_printer); + Intent intent = new Intent(); + intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); + selectItem.setIntent(intent); + } + + // Add the forget menu item if applicable. + FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>) + getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER); + if (provider.isFavoritePrinter(printer.getId())) { + MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer, + Menu.NONE, R.string.print_forget_printer); + Intent intent = new Intent(); + intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); + forgetItem.setIntent(intent); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.string.print_select_printer: { + PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra( + EXTRA_PRINTER_ID); + Activity activity = getActivity(); + if (activity instanceof OnPrinterSelectedListener) { + ((OnPrinterSelectedListener) activity).onPrinterSelected(printerId); + } else { + throw new IllegalStateException("the host activity must implement" + + " OnPrinterSelectedListener"); + } + } return true; + + case R.string.print_forget_printer: { + PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra( + EXTRA_PRINTER_ID); + FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>) + getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER); + provider.forgetFavoritePrinter(printerId); + } return true; + } + return false; + } + + @Override public void onResume() { updateAddPrintersAdapter(); getActivity().invalidateOptionsMenu(); @@ -464,7 +530,7 @@ public final class SelectPrinterFragment extends Fragment { R.layout.printer_list_item, parent, false); } - convertView.setEnabled(isEnabled(position)); + convertView.setEnabled(isActionable(position)); CharSequence title = null; CharSequence subtitle = null; @@ -506,8 +572,7 @@ public final class SelectPrinterFragment extends Fragment { return convertView; } - @Override - public boolean isEnabled(int position) { + public boolean isActionable(int position) { PrinterInfo printer = (PrinterInfo) getItem(position); return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index ffb4c2026291..29e8d1dad952 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -5,7 +5,6 @@ > <!-- Standard permissions granted to the shell. --> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> @@ -64,6 +63,7 @@ <uses-permission android:name="android.permission.SET_SCREEN_COMPATIBILITY" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 09ac2dabcbcb..8d6fe41df8ba 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -71,6 +71,9 @@ <!-- Keyguard --> <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> + <!-- Wifi Display --> + <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" /> + <application android:persistent="true" android:allowClearUserData="false" diff --git a/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png Binary files differnew file mode 100644 index 000000000000..5bbfa4fff6bb --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png Binary files differindex 2d8d07476222..693abf54bdd2 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 000000000000..f256fbbb246a --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 000000000000..b946ec9029f7 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 000000000000..48606a8d0513 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 000000000000..d006f13e4b46 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 000000000000..867947b22d6d --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png Binary files differindex 72209681b265..e3b3eebef89b 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index 02d7fdaa7aec..000000000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index 263f07c4dd06..000000000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png Binary files differindex 8f4cb64a5f23..c6f03c495e07 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-hdpi/search_light.png b/packages/SystemUI/res/drawable-hdpi/search_light.png Binary files differindex 116b1f0946d5..3c0dc4e00b5b 100644 --- a/packages/SystemUI/res/drawable-hdpi/search_light.png +++ b/packages/SystemUI/res/drawable-hdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-hdpi/search_light_land.png b/packages/SystemUI/res/drawable-hdpi/search_light_land.png Binary files differnew file mode 100644 index 000000000000..731f19b5179f --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png Binary files differnew file mode 100644 index 000000000000..1a58144aaf6d --- /dev/null +++ b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png Binary files differnew file mode 100644 index 000000000000..a12519ebdc33 --- /dev/null +++ b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 000000000000..ce414549a095 --- /dev/null +++ b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 000000000000..b0b456198c6d --- /dev/null +++ b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png Binary files differnew file mode 100644 index 000000000000..2856e0985158 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png Binary files differindex 399db009a67d..15340d37d9c6 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 000000000000..b1e984c7c0a0 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 000000000000..ceda1bb6398a --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 000000000000..25fc7593beb1 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 000000000000..9dfc3c6537fa --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 000000000000..82f4113b39b1 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png Binary files differindex 8c2dc6885cfd..cc817943c1fc 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index 09ae4095d6c2..000000000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index 780cfc8b32f7..000000000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png Binary files differindex 2142147beba5..1c2d7aa3c480 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-mdpi/search_light.png b/packages/SystemUI/res/drawable-mdpi/search_light.png Binary files differindex 7a70984378ce..8010ce7e2e59 100644 --- a/packages/SystemUI/res/drawable-mdpi/search_light.png +++ b/packages/SystemUI/res/drawable-mdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-mdpi/search_light_land.png b/packages/SystemUI/res/drawable-mdpi/search_light_land.png Binary files differnew file mode 100644 index 000000000000..a4d82f05b098 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 000000000000..72269f207a5a --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png Binary files differindex c0032e23f170..e3cc9b0307f6 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 000000000000..47be502e0a31 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 000000000000..4b12809b9c96 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 000000000000..945c6060055a --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 000000000000..0a3f73e1b7bb --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 000000000000..398cbef8baa0 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png Binary files differindex bffbf555b47f..65d15b530608 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index 48f90ac09dd1..000000000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index 621c045378e0..000000000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png Binary files differindex b0ea8e0b8158..fbd4d6b02ca4 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light.png b/packages/SystemUI/res/drawable-xhdpi/search_light.png Binary files differindex e2aed092dc9e..6d46fddcc195 100644 --- a/packages/SystemUI/res/drawable-xhdpi/search_light.png +++ b/packages/SystemUI/res/drawable-xhdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png Binary files differnew file mode 100644 index 000000000000..b62c74ed0864 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 000000000000..efc9b0465881 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png Binary files differindex a3cc08de1f87..e15981adfbf6 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 000000000000..8e225af212ef --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 000000000000..937202bdf809 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 000000000000..4621d185380a --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 000000000000..a1ab61be9c7d --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 000000000000..ea42a7f670b5 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png Binary files differindex ab841d282d21..1a5d26a371ba 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index b07be828f369..000000000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index f02d0ab10fa4..000000000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png Binary files differindex aac34287dee0..86df881d4142 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light.png b/packages/SystemUI/res/drawable-xxhdpi/search_light.png Binary files differindex e5ef85d8fb1b..774220779087 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/search_light.png +++ b/packages/SystemUI/res/drawable-xxhdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png Binary files differnew file mode 100644 index 000000000000..f3645772625e --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml b/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml new file mode 100644 index 000000000000..70db2a9b4e34 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright 2013, 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. + */ +--> +<animation-list + xmlns:android="http://schemas.android.com/apk/res/android" + android:oneshot="false"> + <item android:drawable="@drawable/ic_qs_cast_connecting_0" android:duration="500" /> + <item android:drawable="@drawable/ic_qs_cast_connecting_1" android:duration="500" /> + <item android:drawable="@drawable/ic_qs_cast_connecting_2" android:duration="500" /> + <item android:drawable="@drawable/ic_qs_cast_connecting_1" android:duration="500" /> +</animation-list> diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml index b2ba25a38bd7..0c0be29bdbe4 100644 --- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml @@ -24,6 +24,7 @@ android:id="@+id/recents_root" android:layout_height="match_parent" android:layout_width="match_parent" + android:foreground="@drawable/bg_protect" systemui:recentItemLayout="@layout/status_bar_recent_item" > <FrameLayout diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index aa365aedcfb1..5488a8785c99 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -311,7 +311,7 @@ android:layout_height="80dp" android:layout_width="match_parent" android:layout_gravity="center_vertical" - android:src="@drawable/search_light" + android:src="@drawable/search_light_land" android:scaleType="center" android:visibility="gone" android:contentDescription="@string/accessibility_search_light" diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index d7312dfd5326..eb6690839a30 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -95,12 +95,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" /> - <!-- battery must be padded below by 2px to match assets --> + <!-- battery must be padded below to match assets --> <com.android.systemui.BatteryMeterView android:id="@+id/battery" android:layout_height="16dp" android:layout_width="10.5dp" - android:layout_marginBottom="2px" + android:layout_marginBottom="0.33dp" android:layout_marginStart="4dip" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/status_bar_recent_panel.xml b/packages/SystemUI/res/layout/status_bar_recent_panel.xml index e41475b41a56..2f3968dff662 100644 --- a/packages/SystemUI/res/layout/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout/status_bar_recent_panel.xml @@ -24,6 +24,7 @@ android:id="@+id/recents_root" android:layout_height="match_parent" android:layout_width="match_parent" + android:foreground="@drawable/bg_protect" systemui:recentItemLayout="@layout/status_bar_recent_item" > <FrameLayout diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e6fcdff43832..e36ca8e6ae7c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -490,10 +490,8 @@ <string name="quick_settings_wifi_no_network">No Network</string> <!-- QuickSettings: Wifi (Off) [CHAR LIMIT=NONE] --> <string name="quick_settings_wifi_off_label">Wi-Fi Off</string> - <!-- QuickSettings: Wifi display [CHAR LIMIT=NONE] --> - <string name="quick_settings_wifi_display_label">Wi-Fi Display</string> - <!-- QuickSettings: Wifi display [CHAR LIMIT=NONE] --> - <string name="quick_settings_wifi_display_no_connection_label">Wireless Display</string> + <!-- QuickSettings: Remote display [CHAR LIMIT=NONE] --> + <string name="quick_settings_remote_display_no_connection_label">Cast Screen</string> <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] --> <string name="quick_settings_brightness_dialog_title">Brightness</string> <!-- QuickSettings: Brightness dialog auto brightness button [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index b6e03e143c7e..13aafb261a8b 100755 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -66,7 +66,7 @@ public class BatteryMeterView extends View implements DemoMode { private final RectF mFrame = new RectF(); private final RectF mButtonFrame = new RectF(); private final RectF mClipFrame = new RectF(); - private final Rect mBoltFrame = new Rect(); + private final RectF mBoltFrame = new RectF(); private class BatteryTracker extends BroadcastReceiver { public static final int UNKNOWN_LEVEL = -1; @@ -319,10 +319,10 @@ public class BatteryMeterView extends View implements DemoMode { if (tracker.plugged) { // draw the bolt - final int bl = (int)(mFrame.left + mFrame.width() / 4.5f); - final int bt = (int)(mFrame.top + mFrame.height() / 6f); - final int br = (int)(mFrame.right - mFrame.width() / 7f); - final int bb = (int)(mFrame.bottom - mFrame.height() / 10f); + final float bl = mFrame.left + mFrame.width() / 4.5f; + final float bt = mFrame.top + mFrame.height() / 6f; + final float br = mFrame.right - mFrame.width() / 7f; + final float bb = mFrame.bottom - mFrame.height() / 10f; if (mBoltFrame.left != bl || mBoltFrame.top != bt || mBoltFrame.right != br || mBoltFrame.bottom != bb) { mBoltFrame.set(bl, bt, br, bb); diff --git a/packages/SystemUI/src/com/android/systemui/DessertCase.java b/packages/SystemUI/src/com/android/systemui/DessertCase.java index dd4c0182fe66..d797e38baaf1 100644 --- a/packages/SystemUI/src/com/android/systemui/DessertCase.java +++ b/packages/SystemUI/src/com/android/systemui/DessertCase.java @@ -36,7 +36,8 @@ public class DessertCase extends Activity { if (pm.getComponentEnabledSetting(cn) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { Slog.v("DessertCase", "ACHIEVEMENT UNLOCKED"); pm.setComponentEnabledSetting(cn, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP); } mView = new DessertCaseView(this); diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java index 6fce732c3f1f..4147155ad070 100644 --- a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java +++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java @@ -496,7 +496,6 @@ public class DessertCaseView extends FrameLayout { } public static class RescalingContainer extends FrameLayout { - private static final int SYSTEM_UI_MODE_800 = 0x00000800; private DessertCaseView mView; private float mDarkness; @@ -509,7 +508,7 @@ public class DessertCaseView extends FrameLayout { | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | SYSTEM_UI_MODE_800 + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); } diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 4b0c2cb31ee8..8092ab3e4243 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -27,13 +27,16 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Region.Op; import android.opengl.GLUtils; import android.os.SystemProperties; import android.renderscript.Matrix4f; import android.service.wallpaper.WallpaperService; import android.util.Log; +import android.view.Display; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.WindowManager; @@ -107,10 +110,12 @@ public class ImageWallpaper extends WallpaperService { private WallpaperObserver mReceiver; Bitmap mBackground; + int mBackgroundWidth = -1, mBackgroundHeight = -1; int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; int mLastRotation = -1; - float mXOffset; - float mYOffset; + float mXOffset = 0.5f; + float mYOffset = 0.5f; + float mScale = 1f; boolean mVisible = true; boolean mRedrawNeeded; @@ -155,6 +160,8 @@ public class ImageWallpaper extends WallpaperService { mLastSurfaceWidth = mLastSurfaceHeight = -1; mBackground = null; + mBackgroundWidth = -1; + mBackgroundHeight = -1; mRedrawNeeded = true; drawFrame(); } @@ -173,6 +180,8 @@ public class ImageWallpaper extends WallpaperService { } mBackground.recycle(); mBackground = null; + mBackgroundWidth = -1; + mBackgroundHeight = -1; mWallpaperManager.forgetLoadedWallpaper(); } } @@ -204,21 +213,40 @@ public class ImageWallpaper extends WallpaperService { } } - @Override - public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) { - super.onDesiredSizeChanged(desiredWidth, desiredHeight); - SurfaceHolder surfaceHolder = getSurfaceHolder(); - if (surfaceHolder != null) { - updateSurfaceSize(surfaceHolder); + void updateSurfaceSize(SurfaceHolder surfaceHolder) { + Point p = getDefaultDisplaySize(); + + // Load background image dimensions, if we haven't saved them yet + if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) { + // Need to load the image to get dimensions + mWallpaperManager.forgetLoadedWallpaper(); + updateWallpaperLocked(); + if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) { + // Default to the display size if we can't find the dimensions + mBackgroundWidth = p.x; + mBackgroundHeight = p.y; + } + } + + // Force the wallpaper to cover the screen in both dimensions + int surfaceWidth = Math.max(p.x, mBackgroundWidth); + int surfaceHeight = Math.max(p.y, mBackgroundHeight); + + // If the surface dimensions haven't changed, then just return + final Rect frame = surfaceHolder.getSurfaceFrame(); + if (frame != null) { + final int dw = frame.width(); + final int dh = frame.height(); + if (surfaceWidth == dw && surfaceHeight == dh) { + return; + } } - } - void updateSurfaceSize(SurfaceHolder surfaceHolder) { if (FIXED_SIZED_SURFACE) { // Used a fixed size surface, because we are special. We can do // this because we know the current design of window animations doesn't // cause this to break. - surfaceHolder.setFixedSize(getDesiredMinimumWidth(), getDesiredMinimumHeight()); + surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight); } else { surfaceHolder.setSizeFromLayout(); } @@ -298,13 +326,30 @@ public class ImageWallpaper extends WallpaperService { drawFrame(); } + private Point getDefaultDisplaySize() { + Point p = new Point(); + Context c = ImageWallpaper.this.getApplicationContext(); + WindowManager wm = (WindowManager)c.getSystemService(Context.WINDOW_SERVICE); + Display d = wm.getDefaultDisplay(); + d.getRealSize(p); + return p; + } + void drawFrame() { + int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)). + getDefaultDisplay().getRotation(); + + // Sometimes a wallpaper is not large enough to cover the screen in one dimension. + // Call updateSurfaceSize -- it will only actually do the update if the dimensions + // should change + if (newRotation != mLastRotation) { + // Update surface size (if necessary) + updateSurfaceSize(getSurfaceHolder()); + } SurfaceHolder sh = getSurfaceHolder(); final Rect frame = sh.getSurfaceFrame(); final int dw = frame.width(); final int dh = frame.height(); - int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)). - getDefaultDisplay().getRotation(); boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth || dh != mLastSurfaceHeight; boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation; @@ -343,10 +388,21 @@ public class ImageWallpaper extends WallpaperService { } } - final int availw = dw - mBackground.getWidth(); - final int availh = dh - mBackground.getHeight(); - int xPixels = availw < 0 ? (int)(availw * mXOffset + .5f) : (availw / 2); - int yPixels = availh < 0 ? (int)(availh * mYOffset + .5f) : (availh / 2); + // Center the scaled image + mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(), + dh / (float) mBackground.getHeight())); + final int availw = dw - (int) (mBackground.getWidth() * mScale); + final int availh = dh - (int) (mBackground.getHeight() * mScale); + int xPixels = availw / 2; + int yPixels = availh / 2; + + // Adjust the image for xOffset/yOffset values. If window manager is handling offsets, + // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels + // will remain unchanged + final int availwUnscaled = dw - mBackground.getWidth(); + final int availhUnscaled = dh - mBackground.getHeight(); + if (availwUnscaled < 0) xPixels += (int)(availwUnscaled * (mXOffset - .5f) + .5f); + if (availhUnscaled < 0) yPixels += (int)(availhUnscaled * (mYOffset - .5f) + .5f); mOffsetsChanged = false; mRedrawNeeded = false; @@ -354,8 +410,6 @@ public class ImageWallpaper extends WallpaperService { mLastSurfaceWidth = dw; mLastSurfaceHeight = dh; } - mLastXTranslation = xPixels; - mLastYTranslation = yPixels; if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) { if (DEBUG) { Log.d(TAG, "Suppressed drawFrame since the image has not " @@ -363,6 +417,8 @@ public class ImageWallpaper extends WallpaperService { } return; } + mLastXTranslation = xPixels; + mLastYTranslation = yPixels; if (DEBUG) { Log.d(TAG, "Redrawing wallpaper"); @@ -391,7 +447,11 @@ public class ImageWallpaper extends WallpaperService { Throwable exception = null; try { mBackground = null; + mBackgroundWidth = -1; + mBackgroundHeight = -1; mBackground = mWallpaperManager.getBitmap(); + mBackgroundWidth = mBackground.getWidth(); + mBackgroundHeight = mBackground.getHeight(); } catch (RuntimeException e) { exception = e; } catch (OutOfMemoryError e) { @@ -400,6 +460,8 @@ public class ImageWallpaper extends WallpaperService { if (exception != null) { mBackground = null; + mBackgroundWidth = -1; + mBackgroundHeight = -1; // Note that if we do fail at this, and the default wallpaper can't // be loaded, we will go into a cycle. Don't do a build where the // default wallpaper can't be loaded. @@ -413,24 +475,27 @@ public class ImageWallpaper extends WallpaperService { } } - private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int x, int y) { + private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) { Canvas c = sh.lockCanvas(); if (c != null) { try { if (DEBUG) { - Log.d(TAG, "Redrawing: x=" + x + ", y=" + y); + Log.d(TAG, "Redrawing: left=" + left + ", top=" + top); } - c.translate(x, y); + final float right = left + mBackground.getWidth() * mScale; + final float bottom = top + mBackground.getHeight() * mScale; if (w < 0 || h < 0) { c.save(Canvas.CLIP_SAVE_FLAG); - c.clipRect(0, 0, mBackground.getWidth(), mBackground.getHeight(), + c.clipRect(left, top, right, bottom, Op.DIFFERENCE); c.drawColor(0xff000000); c.restore(); } if (mBackground != null) { - c.drawBitmap(mBackground, 0, 0, null); + RectF dest = new RectF(left, top, right, bottom); + // add a filter bitmap? + c.drawBitmap(mBackground, null, dest, null); } } finally { sh.unlockCanvasAndPost(c); @@ -441,8 +506,8 @@ public class ImageWallpaper extends WallpaperService { private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) { if (!initGL(sh)) return false; - final float right = left + mBackground.getWidth(); - final float bottom = top + mBackground.getHeight(); + final float right = left + mBackground.getWidth() * mScale; + final float bottom = top + mBackground.getHeight() * mScale; final Rect frame = sh.getSurfaceFrame(); final Matrix4f ortho = new Matrix4f(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java index 9839fe9e6f4d..7d3e870c1a8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java @@ -38,7 +38,7 @@ public class AnimatedImageView extends ImageView { } private void updateAnim() { - Drawable drawable = getDrawable(); + Drawable drawable = mAttached ? getDrawable() : null; if (mAttached && mAnim != null) { mAnim.stop(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 6a2bc5ff1739..ed00398b6531 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar; import android.app.ActivityManager; import android.app.ActivityManagerNative; -import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.app.TaskStackBuilder; @@ -70,6 +69,7 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SearchPanelView; import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; import com.android.systemui.statusbar.policy.NotificationRowLayout; import java.util.ArrayList; @@ -128,7 +128,6 @@ public abstract class BaseStatusBar extends SystemUI implements protected boolean mUseHeadsUp = false; protected IDreamManager mDreamManager; - KeyguardManager mKeyguardManager; PowerManager mPowerManager; protected int mRowHeight; @@ -221,7 +220,6 @@ public abstract class BaseStatusBar extends SystemUI implements mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.checkService(DreamService.DREAM_SERVICE)); - mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mProvisioningObserver.onChange(false); // set up @@ -749,9 +747,7 @@ public abstract class BaseStatusBar extends SystemUI implements Log.w(TAG, "Sending contentIntent failed: " + e); } - KeyguardManager kgm = - (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - if (kgm != null) kgm.exitKeyguardSecurely(null); + KeyguardTouchDelegate.getInstance(mContext).dismiss(); } try { @@ -1056,10 +1052,12 @@ public abstract class BaseStatusBar extends SystemUI implements boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; + final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext); boolean interrupt = (isFullscreen || (isHighPriority && isNoisy)) && isAllowed && mPowerManager.isScreenOn() - && !mKeyguardManager.isKeyguardLocked(); + && !keyguard.isShowingAndNotHidden() + && !keyguard.isInputRestricted(); try { interrupt = interrupt && !mDreamManager.isDreaming(); } catch (RemoteException e) { @@ -1087,8 +1085,7 @@ public abstract class BaseStatusBar extends SystemUI implements } public boolean inKeyguardRestrictedInputMode() { - KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - return km.inKeyguardRestrictedInputMode(); + return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted(); } public void setInteracting(int barWindow, boolean interacting) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index e8173b799586..39333d72d9da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -55,8 +55,7 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_TOGGLE_RECENT_APPS = 13 << MSG_SHIFT; private static final int MSG_PRELOAD_RECENT_APPS = 14 << MSG_SHIFT; private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 15 << MSG_SHIFT; - private static final int MSG_SET_NAVIGATION_ICON_HINTS = 16 << MSG_SHIFT; - private static final int MSG_SET_WINDOW_STATE = 17 << MSG_SHIFT; + private static final int MSG_SET_WINDOW_STATE = 16 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -98,7 +97,6 @@ public class CommandQueue extends IStatusBar.Stub { public void showSearchPanel(); public void hideSearchPanel(); public void cancelPreloadRecentApps(); - public void setNavigationIconHints(int hints); public void setWindowState(int window, int state); } @@ -227,13 +225,6 @@ public class CommandQueue extends IStatusBar.Stub { } } - public void setNavigationIconHints(int hints) { - synchronized (mList) { - mHandler.removeMessages(MSG_SET_NAVIGATION_ICON_HINTS); - mHandler.obtainMessage(MSG_SET_NAVIGATION_ICON_HINTS, hints, 0, null).sendToTarget(); - } - } - public void setWindowState(int window, int state) { synchronized (mList) { // don't coalesce these @@ -318,9 +309,6 @@ public class CommandQueue extends IStatusBar.Stub { case MSG_CANCEL_PRELOAD_RECENT_APPS: mCallbacks.cancelPreloadRecentApps(); break; - case MSG_SET_NAVIGATION_ICON_HINTS: - mCallbacks.setNavigationIconHints(msg.arg1); - break; case MSG_SET_WINDOW_STATE: mCallbacks.setWindowState(msg.arg1, msg.arg2); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java index 8ad538badb57..eb63a548ac42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java @@ -37,6 +37,8 @@ public class BarTransitions { private static final boolean DEBUG = false; private static final boolean DEBUG_COLORS = false; + public static final boolean HIGH_END = ActivityManager.isHighEndGfx(); + public static final int MODE_OPAQUE = 0; public static final int MODE_SEMI_TRANSPARENT = 1; public static final int MODE_TRANSLUCENT = 2; @@ -48,7 +50,6 @@ public class BarTransitions { private final String mTag; private final View mView; - private final boolean mSupportsTransitions = ActivityManager.isHighEndGfx(); private final BarBackgroundDrawable mBarBackground; private int mMode; @@ -57,7 +58,7 @@ public class BarTransitions { mTag = "BarTransitions." + view.getClass().getSimpleName(); mView = view; mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId); - if (mSupportsTransitions) { + if (HIGH_END) { mView.setBackground(mBarBackground); } } @@ -67,18 +68,22 @@ public class BarTransitions { } public void transitionTo(int mode, boolean animate) { + // low-end devices do not support translucent modes, fallback to opaque + if (!HIGH_END && (mode == MODE_SEMI_TRANSPARENT || mode == MODE_TRANSLUCENT)) { + mode = MODE_OPAQUE; + } if (mMode == mode) return; int oldMode = mMode; mMode = mode; if (DEBUG) Log.d(mTag, String.format("%s -> %s animate=%s", modeToString(oldMode), modeToString(mode), animate)); - if (mSupportsTransitions) { - onTransition(oldMode, mMode, animate); - } + onTransition(oldMode, mMode, animate); } protected void onTransition(int oldMode, int newMode, boolean animate) { - applyModeBackground(oldMode, newMode, animate); + if (HIGH_END) { + applyModeBackground(oldMode, newMode, animate); + } } protected void applyModeBackground(int oldMode, int newMode, boolean animate) { @@ -99,6 +104,10 @@ public class BarTransitions { mBarBackground.finishAnimation(); } + public void setContentVisible(boolean visible) { + // for subclasses + } + private static class BarBackgroundDrawable extends Drawable { private final int mOpaque; private final int mSemiTransparent; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java index 5c55f0da4a01..c1646bac974f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java @@ -77,10 +77,11 @@ public class KeyguardTouchDelegate { } public static KeyguardTouchDelegate getInstance(Context context) { - if (sInstance == null) { - sInstance = new KeyguardTouchDelegate(context); + KeyguardTouchDelegate instance = sInstance; + if (instance == null) { + instance = sInstance = new KeyguardTouchDelegate(context); } - return sInstance; + return instance; } public boolean isSecure() { @@ -165,7 +166,21 @@ public class KeyguardTouchDelegate { Slog.e(TAG, "RemoteException launching camera!", e); } } else { - Slog.w(TAG, "dispatch(event): NO SERVICE!"); + Slog.w(TAG, "launchCamera(): NO SERVICE!"); + } + } + + public void dismiss() { + final IKeyguardService service = mService; + if (service != null) { + try { + service.dismiss(); + } catch (RemoteException e) { + // What to do? + Slog.e(TAG, "RemoteException dismissing keyguard!", e); + } + } else { + Slog.w(TAG, "dismiss(): NO SERVICE!"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index 5d4b9958f0d1..a74230badc75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -30,6 +30,9 @@ import com.android.systemui.statusbar.policy.KeyButtonView; public final class NavigationBarTransitions extends BarTransitions { + private static final float KEYGUARD_QUIESCENT_ALPHA = 0.5f; + private static final int CONTENT_FADE_DURATION = 200; + private final NavigationBarView mView; private final IStatusBarService mBarService; @@ -73,18 +76,57 @@ public final class NavigationBarTransitions extends BarTransitions { private void applyMode(int mode, boolean animate, boolean force) { // apply to key buttons - final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT; - final float alpha = isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f; - setKeyButtonViewQuiescentAlpha(mView.getBackButton(), alpha, animate); + final float alpha = alphaForMode(mode); setKeyButtonViewQuiescentAlpha(mView.getHomeButton(), alpha, animate); setKeyButtonViewQuiescentAlpha(mView.getRecentsButton(), alpha, animate); setKeyButtonViewQuiescentAlpha(mView.getMenuButton(), alpha, animate); - setKeyButtonViewQuiescentAlpha(mView.getCameraButton(), alpha, animate); + + setKeyButtonViewQuiescentAlpha(mView.getSearchLight(), KEYGUARD_QUIESCENT_ALPHA, animate); + setKeyButtonViewQuiescentAlpha(mView.getCameraButton(), KEYGUARD_QUIESCENT_ALPHA, animate); + + applyBackButtonQuiescentAlpha(mode, animate); // apply to lights out applyLightsOut(mode == MODE_LIGHTS_OUT, animate, force); } + private float alphaForMode(int mode) { + final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT; + return isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f; + } + + public void applyBackButtonQuiescentAlpha(int mode, boolean animate) { + float backAlpha = 0; + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getSearchLight()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getCameraButton()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getHomeButton()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getRecentsButton()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getMenuButton()); + if (backAlpha > 0) { + setKeyButtonViewQuiescentAlpha(mView.getBackButton(), backAlpha, animate); + } + } + + private static float maxVisibleQuiescentAlpha(float max, View v) { + if ((v instanceof KeyButtonView) && v.isShown()) { + return Math.max(max, ((KeyButtonView)v).getQuiescentAlpha()); + } + return max; + } + + @Override + public void setContentVisible(boolean visible) { + final float alpha = visible ? 1 : 0; + fadeContent(mView.getCameraButton(), alpha); + fadeContent(mView.getSearchLight(), alpha); + } + + private void fadeContent(View v, float alpha) { + if (v != null) { + v.animate().alpha(alpha).setDuration(CONTENT_FADE_DURATION); + } + } + private void setKeyButtonViewQuiescentAlpha(View button, float alpha, boolean animate) { if (button instanceof KeyButtonView) { ((KeyButtonView) button).setQuiescentAlpha(alpha, animate); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index d1c4109c4c92..839016d7a1de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -17,6 +17,10 @@ package com.android.systemui.statusbar.phone; import android.animation.LayoutTransition; +import android.animation.LayoutTransition.TransitionListener; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; import android.app.ActivityManagerNative; import android.app.StatusBarManager; import android.app.admin.DevicePolicyManager; @@ -48,12 +52,12 @@ import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.DelegateViewHelper; import com.android.systemui.statusbar.policy.DeadZone; +import com.android.systemui.statusbar.policy.KeyButtonView; import java.io.FileDescriptor; import java.io.PrintWriter; public class NavigationBarView extends LinearLayout { - private static final int CAMERA_BUTTON_FADE_DURATION = 200; final static boolean DEBUG = false; final static String TAG = "PhoneStatusBar/NavigationBarView"; @@ -89,6 +93,54 @@ public class NavigationBarView extends LinearLayout { // used to disable the camera icon in navbar when disabled by DPM private boolean mCameraDisabledByDpm; + // performs manual animation in sync with layout transitions + private final NavTransitionListener mTransitionListener = new NavTransitionListener(); + + private class NavTransitionListener implements TransitionListener { + private boolean mBackTransitioning; + private boolean mHomeAppearing; + private long mStartDelay; + private long mDuration; + private TimeInterpolator mInterpolator; + + @Override + public void startTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + if (view.getId() == R.id.back) { + mBackTransitioning = true; + } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { + mHomeAppearing = true; + mStartDelay = transition.getStartDelay(transitionType); + mDuration = transition.getDuration(transitionType); + mInterpolator = transition.getInterpolator(transitionType); + } + } + + @Override + public void endTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + if (view.getId() == R.id.back) { + mBackTransitioning = false; + } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { + mHomeAppearing = false; + } + } + + public void onBackAltCleared() { + // When dismissing ime during unlock, force the back button to run the same appearance + // animation as home (if we catch this condition early enough). + if (!mBackTransitioning && getBackButton().getVisibility() == VISIBLE + && mHomeAppearing && getHomeButton().getAlpha() == 0) { + getBackButton().setAlpha(0); + ValueAnimator a = ObjectAnimator.ofFloat(getBackButton(), "alpha", 0, 1); + a.setStartDelay(mStartDelay); + a.setDuration(mDuration); + a.setInterpolator(mInterpolator); + a.start(); + } + } + } + // simplified click handler to be used when device is in accessibility mode private final OnClickListener mAccessibilityClickListener = new OnClickListener() { @Override @@ -108,12 +160,12 @@ public class NavigationBarView extends LinearLayout { case MotionEvent.ACTION_DOWN: // disable search gesture while interacting with camera mDelegateHelper.setDisabled(true); - transitionCameraAndSearchButtonAlpha(0.0f); + mBarTransitions.setContentVisible(false); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mDelegateHelper.setDisabled(false); - transitionCameraAndSearchButtonAlpha(1.0f); + mBarTransitions.setContentVisible(true); break; } return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event); @@ -163,17 +215,6 @@ public class NavigationBarView extends LinearLayout { watchForDevicePolicyChanges(); } - protected void transitionCameraAndSearchButtonAlpha(float alpha) { - View cameraButtonView = getCameraButton(); - if (cameraButtonView != null) { - cameraButtonView.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION); - } - View searchLight = getSearchLight(); - if (searchLight != null) { - searchLight.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION); - } - } - private void watchForDevicePolicyChanges() { final IntentFilter filter = new IntentFilter(); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); @@ -277,7 +318,10 @@ public class NavigationBarView extends LinearLayout { public void setNavigationIconHints(int hints, boolean force) { if (!force && hints == mNavigationIconHints) return; - + final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) { + mTransitionListener.onBackAltCleared(); + } if (DEBUG) { android.widget.Toast.makeText(mContext, "Navigation icon hints = " + hints, @@ -286,15 +330,7 @@ public class NavigationBarView extends LinearLayout { mNavigationIconHints = hints; - getBackButton().setAlpha( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f); - getHomeButton().setAlpha( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f); - getRecentsButton().setAlpha( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f); - - ((ImageView)getBackButton()).setImageDrawable( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT)) + ((ImageView)getBackButton()).setImageDrawable(backAlt ? (mVertical ? mBackAltLandIcon : mBackAltIcon) : (mVertical ? mBackLandIcon : mBackIcon)); @@ -322,13 +358,20 @@ public class NavigationBarView extends LinearLayout { setSlippery(disableHome && disableRecent && disableBack && disableSearch); } - if (!mScreenOn && mCurrentView != null) { - ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); - LayoutTransition lt = navButtons == null ? null : navButtons.getLayoutTransition(); + ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); + if (navButtons != null) { + LayoutTransition lt = navButtons.getLayoutTransition(); if (lt != null) { - lt.disableTransitionType( - LayoutTransition.CHANGE_APPEARING | LayoutTransition.CHANGE_DISAPPEARING | - LayoutTransition.APPEARING | LayoutTransition.DISAPPEARING); + if (!lt.getTransitionListeners().contains(mTransitionListener)) { + lt.addTransitionListener(mTransitionListener); + } + if (!mScreenOn && mCurrentView != null) { + lt.disableTransitionType( + LayoutTransition.CHANGE_APPEARING | + LayoutTransition.CHANGE_DISAPPEARING | + LayoutTransition.APPEARING | + LayoutTransition.DISAPPEARING); + } } } @@ -336,12 +379,17 @@ public class NavigationBarView extends LinearLayout { getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); - final boolean shouldShowSearch = disableHome && !disableSearch; - getSearchLight().setVisibility(shouldShowSearch ? View.VISIBLE : View.GONE); - final View cameraButton = getCameraButton(); - if (cameraButton != null) { - cameraButton.setVisibility( - shouldShowSearch && !mCameraDisabledByDpm ? View.VISIBLE : View.GONE); + final boolean showSearch = disableHome && !disableSearch; + final boolean showCamera = showSearch && !mCameraDisabledByDpm; + setVisibleOrGone(getSearchLight(), showSearch); + setVisibleOrGone(getCameraButton(), showCamera); + + mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/); + } + + private void setVisibleOrGone(View view, boolean visible) { + if (view != null) { + view.setVisibility(visible ? VISIBLE : GONE); } } @@ -574,28 +622,31 @@ public class NavigationBarView extends LinearLayout { mVertical ? "true" : "false", mShowMenu ? "true" : "false")); - final View back = getBackButton(); - final View home = getHomeButton(); - final View recent = getRecentsButton(); - final View menu = getMenuButton(); - - pw.println(" back: " - + PhoneStatusBar.viewInfo(back) - + " " + visibilityToString(back.getVisibility()) - ); - pw.println(" home: " - + PhoneStatusBar.viewInfo(home) - + " " + visibilityToString(home.getVisibility()) - ); - pw.println(" rcnt: " - + PhoneStatusBar.viewInfo(recent) - + " " + visibilityToString(recent.getVisibility()) - ); - pw.println(" menu: " - + PhoneStatusBar.viewInfo(menu) - + " " + visibilityToString(menu.getVisibility()) - ); + dumpButton(pw, "back", getBackButton()); + dumpButton(pw, "home", getHomeButton()); + dumpButton(pw, "rcnt", getRecentsButton()); + dumpButton(pw, "menu", getMenuButton()); + dumpButton(pw, "srch", getSearchLight()); + dumpButton(pw, "cmra", getCameraButton()); + pw.println(" }"); } + private static void dumpButton(PrintWriter pw, String caption, View button) { + pw.print(" " + caption + ": "); + if (button == null) { + pw.print("null"); + } else { + pw.print(PhoneStatusBar.viewInfo(button) + + " " + visibilityToString(button.getVisibility()) + + " alpha=" + button.getAlpha() + ); + if (button instanceof KeyButtonView) { + pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha()); + pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha()); + } + } + pw.println(); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 39a9ba7d3dd8..bbac4efcbd20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -53,6 +53,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -632,6 +633,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } } + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mBroadcastReceiver.onReceive(mContext, + new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF)); + // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -649,14 +654,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { @Override protected void onShowSearchPanel() { if (mNavigationBarView != null) { - mNavigationBarView.transitionCameraAndSearchButtonAlpha(0.0f); + mNavigationBarView.getBarTransitions().setContentVisible(false); } } @Override protected void onHideSearchPanel() { if (mNavigationBarView != null) { - mNavigationBarView.transitionCameraAndSearchButtonAlpha(1.0f); + mNavigationBarView.getBarTransitions().setContentVisible(true); } } @@ -802,7 +807,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } private void repositionNavigationBar() { - if (mNavigationBarView == null) return; + if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return; prepareNavigationBarView(); @@ -1807,8 +1812,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { return mGestureRec; } - @Override // CommandQueue - public void setNavigationIconHints(int hints) { + private void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; mNavigationIconHints = hints; @@ -2045,7 +2049,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || ((vis & InputMethodService.IME_VISIBLE) != 0); - mCommandQueue.setNavigationIconHints( + setNavigationIconHints( altBack ? (mNavigationIconHints | NAVIGATION_HINT_BACK_ALT) : (mNavigationIconHints & ~NAVIGATION_HINT_BACK_ALT)); if (mQS != null) mQS.setImeWindowStatus(vis > 0); @@ -2101,9 +2105,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } public void tickerHalting() { - mStatusBarContents.setVisibility(View.VISIBLE); + if (mStatusBarContents.getVisibility() != View.VISIBLE) { + mStatusBarContents.setVisibility(View.VISIBLE); + mStatusBarContents + .startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); + } mTickerView.setVisibility(View.GONE); - mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); // we do not animate the ticker away at this point, just get rid of it (b/6992707) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java index 37504fb8e88c..e7b8fa1e1fa4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java @@ -37,9 +37,8 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LevelListDrawable; import android.hardware.display.DisplayManager; -import android.hardware.display.WifiDisplayStatus; +import android.media.MediaRouter; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Handler; @@ -62,6 +61,7 @@ import android.view.WindowManagerGlobal; import android.widget.ImageView; import android.widget.TextView; +import com.android.internal.app.MediaRouteDialogPresenter; import com.android.systemui.R; import com.android.systemui.statusbar.phone.QuickSettingsModel.ActivityState; import com.android.systemui.statusbar.phone.QuickSettingsModel.BluetoothState; @@ -92,9 +92,7 @@ class QuickSettings { private QuickSettingsModel mModel; private ViewGroup mContainerView; - private DisplayManager mDisplayManager; private DevicePolicyManager mDevicePolicyManager; - private WifiDisplayStatus mWifiDisplayStatus; private PhoneStatusBar mStatusBarService; private BluetoothState mBluetoothState; private BluetoothAdapter mBluetoothAdapter; @@ -118,13 +116,11 @@ class QuickSettings { new ArrayList<QuickSettingsTileView>(); public QuickSettings(Context context, QuickSettingsContainerView container) { - mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); mContext = context; mContainerView = container; mModel = new QuickSettingsModel(context); - mWifiDisplayStatus = new WifiDisplayStatus(); mBluetoothState = new QuickSettingsModel.BluetoothState(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); @@ -171,7 +167,6 @@ class QuickSettings { mLocationController = locationController; setupQuickSettings(); - updateWifiDisplayStatus(); updateResources(); applyLocationEnabledStatus(); @@ -314,11 +309,19 @@ class QuickSettings { collapsePanels(); final UserManager um = UserManager.get(mContext); if (um.getUsers(true).size() > 1) { - try { - WindowManagerGlobal.getWindowManagerService().lockNow(null); - } catch (RemoteException e) { - Log.e(TAG, "Couldn't show user switcher", e); - } + // Since keyguard and systemui were merged into the same process to save + // memory, they share the same Looper and graphics context. As a result, + // there's no way to allow concurrent animation while keyguard inflates. + // The workaround is to add a slight delay to allow the animation to finish. + mHandler.postDelayed(new Runnable() { + public void run() { + try { + WindowManagerGlobal.getWindowManagerService().lockNow(null); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't show user switcher", e); + } + } + }, 400); // TODO: ideally this would be tied to the collapse of the panel } else { Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent( mContext, v, ContactsContract.Profile.CONTENT_URI, @@ -668,20 +671,33 @@ class QuickSettings { }); parent.addView(alarmTile); - // Wifi Display - QuickSettingsBasicTile wifiDisplayTile + // Remote Display + QuickSettingsBasicTile remoteDisplayTile = new QuickSettingsBasicTile(mContext); - wifiDisplayTile.setImageResource(R.drawable.ic_qs_remote_display); - wifiDisplayTile.setOnClickListener(new View.OnClickListener() { + remoteDisplayTile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - startSettingsActivity(android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS); + collapsePanels(); + + final Dialog[] dialog = new Dialog[1]; + dialog[0] = MediaRouteDialogPresenter.createDialog(mContext, + MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + new View.OnClickListener() { + @Override + public void onClick(View v) { + dialog[0].dismiss(); + startSettingsActivity( + android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS); + } + }); + dialog[0].getWindow().setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + dialog[0].show(); } }); - mModel.addWifiDisplayTile(wifiDisplayTile, - new QuickSettingsModel.BasicRefreshCallback(wifiDisplayTile) + mModel.addRemoteDisplayTile(remoteDisplayTile, + new QuickSettingsModel.BasicRefreshCallback(remoteDisplayTile) .setShowWhenEnabled(true)); - parent.addView(wifiDisplayTile); + parent.addView(remoteDisplayTile); if (SHOW_IME_TILE || DEBUG_GONE_TILES) { // IME @@ -816,15 +832,6 @@ class QuickSettings { dialog.show(); } - private void updateWifiDisplayStatus() { - mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus(); - applyWifiDisplayStatus(); - } - - private void applyWifiDisplayStatus() { - mModel.onWifiDisplayStateChanged(mWifiDisplayStatus); - } - private void applyBluetoothStatus() { mModel.onBluetoothStateChange(mBluetoothState); } @@ -848,12 +855,7 @@ class QuickSettings { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED.equals(action)) { - WifiDisplayStatus status = (WifiDisplayStatus)intent.getParcelableExtra( - DisplayManager.EXTRA_WIFI_DISPLAY_STATUS); - mWifiDisplayStatus = status; - applyWifiDisplayStatus(); - } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { + if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); mBluetoothState.enabled = (state == BluetoothAdapter.STATE_ON); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java index 15d655c998d4..42201c5a2d98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java @@ -27,7 +27,8 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.drawable.Drawable; -import android.hardware.display.WifiDisplayStatus; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; import android.net.ConnectivityManager; import android.os.Handler; import android.os.UserHandle; @@ -57,7 +58,6 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, BrightnessStateChangeCallback, RotationLockControllerCallback, LocationSettingsChangeCallback { - // Sett InputMethoManagerService private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; @@ -199,6 +199,30 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, } } + /** Callback for changes to remote display routes. */ + private class RemoteDisplayRouteCallback extends MediaRouter.SimpleCallback { + @Override + public void onRouteAdded(MediaRouter router, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteChanged(MediaRouter router, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteRemoved(MediaRouter router, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) { + updateRemoteDisplays(); + } + } + private final Context mContext; private final Handler mHandler; private final CurrentUserTracker mUserTracker; @@ -206,6 +230,9 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, private final BugreportObserver mBugreportObserver; private final BrightnessObserver mBrightnessObserver; + private final MediaRouter mMediaRouter; + private final RemoteDisplayRouteCallback mRemoteDisplayRouteCallback; + private final boolean mHasMobileData; private QuickSettingsTileView mUserTile; @@ -228,9 +255,9 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, private RefreshCallback mWifiCallback; private WifiState mWifiState = new WifiState(); - private QuickSettingsTileView mWifiDisplayTile; - private RefreshCallback mWifiDisplayCallback; - private State mWifiDisplayState = new State(); + private QuickSettingsTileView mRemoteDisplayTile; + private RefreshCallback mRemoteDisplayCallback; + private State mRemoteDisplayState = new State(); private QuickSettingsTileView mRSSITile; private RefreshCallback mRSSICallback; @@ -278,12 +305,14 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mContext = context; mHandler = new Handler(); mUserTracker = new CurrentUserTracker(mContext) { + @Override public void onUserSwitched(int newUserId) { mBrightnessObserver.startObserving(); refreshRotationLockTile(); onBrightnessLevelChanged(); onNextAlarmChanged(); onBugreportChanged(); + rebindMediaRouterAsCurrentUser(); } }; @@ -294,6 +323,11 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mBrightnessObserver = new BrightnessObserver(mHandler); mBrightnessObserver.startObserving(); + mMediaRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + rebindMediaRouterAsCurrentUser(); + + mRemoteDisplayRouteCallback = new RemoteDisplayRouteCallback(); + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); mHasMobileData = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); @@ -621,24 +655,58 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mBugreportCallback.refreshView(mBugreportTile, mBugreportState); } - // Wifi Display - void addWifiDisplayTile(QuickSettingsTileView view, RefreshCallback cb) { - mWifiDisplayTile = view; - mWifiDisplayCallback = cb; + // Remote Display + void addRemoteDisplayTile(QuickSettingsTileView view, RefreshCallback cb) { + mRemoteDisplayTile = view; + mRemoteDisplayCallback = cb; + final int[] count = new int[1]; + mRemoteDisplayTile.setOnPrepareListener(new QuickSettingsTileView.OnPrepareListener() { + @Override + public void onPrepare() { + mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + mRemoteDisplayRouteCallback, + MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); + updateRemoteDisplays(); + } + @Override + public void onUnprepare() { + mMediaRouter.removeCallback(mRemoteDisplayRouteCallback); + } + }); + + updateRemoteDisplays(); + } + + private void rebindMediaRouterAsCurrentUser() { + mMediaRouter.rebindAsUser(mUserTracker.getCurrentUserId()); } - public void onWifiDisplayStateChanged(WifiDisplayStatus status) { - mWifiDisplayState.enabled = - (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON); - if (status.getActiveDisplay() != null) { - mWifiDisplayState.label = status.getActiveDisplay().getFriendlyDisplayName(); - mWifiDisplayState.iconId = R.drawable.ic_qs_remote_display_connected; + + private void updateRemoteDisplays() { + MediaRouter.RouteInfo connectedRoute = mMediaRouter.getSelectedRoute( + MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY); + boolean enabled = connectedRoute != null + && connectedRoute.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY); + boolean connecting; + if (enabled) { + connecting = connectedRoute.isConnecting(); } else { - mWifiDisplayState.label = mContext.getString( - R.string.quick_settings_wifi_display_no_connection_label); - mWifiDisplayState.iconId = R.drawable.ic_qs_remote_display; + connectedRoute = null; + connecting = false; + enabled = mMediaRouter.isRouteAvailable(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE); } - mWifiDisplayCallback.refreshView(mWifiDisplayTile, mWifiDisplayState); + mRemoteDisplayState.enabled = enabled; + if (connectedRoute != null) { + mRemoteDisplayState.label = connectedRoute.getName().toString(); + mRemoteDisplayState.iconId = connecting ? + R.drawable.ic_qs_cast_connecting : R.drawable.ic_qs_cast_connected; + } else { + mRemoteDisplayState.label = mContext.getString( + R.string.quick_settings_remote_display_no_connection_label); + mRemoteDisplayState.iconId = R.drawable.ic_qs_cast_available; + } + mRemoteDisplayCallback.refreshView(mRemoteDisplayTile, mRemoteDisplayState); } // IME diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java index 3d520f7e400c..ad182940780d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java @@ -21,6 +21,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewParent; import android.widget.FrameLayout; /** @@ -31,14 +32,14 @@ class QuickSettingsTileView extends FrameLayout { private int mContentLayoutId; private int mColSpan; - private int mRowSpan; + private boolean mPrepared; + private OnPrepareListener mOnPrepareListener; public QuickSettingsTileView(Context context, AttributeSet attrs) { super(context, attrs); mContentLayoutId = -1; mColSpan = 1; - mRowSpan = 1; } void setColumnSpan(int span) { @@ -77,4 +78,72 @@ class QuickSettingsTileView extends FrameLayout { } super.setVisibility(vis); } + + public void setOnPrepareListener(OnPrepareListener listener) { + if (mOnPrepareListener != listener) { + mOnPrepareListener = listener; + mPrepared = false; + post(new Runnable() { + @Override + public void run() { + updatePreparedState(); + } + }); + } + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + updatePreparedState(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updatePreparedState(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + updatePreparedState(); + } + + private void updatePreparedState() { + if (mOnPrepareListener != null) { + if (isParentVisible()) { + if (!mPrepared) { + mPrepared = true; + mOnPrepareListener.onPrepare(); + } + } else if (mPrepared) { + mPrepared = false; + mOnPrepareListener.onUnprepare(); + } + } + } + + private boolean isParentVisible() { + if (!isAttachedToWindow()) { + return false; + } + for (ViewParent current = getParent(); current instanceof View; + current = current.getParent()) { + View view = (View)current; + if (view.getVisibility() != VISIBLE) { + return false; + } + } + return true; + } + + /** + * Called when the view's parent becomes visible or invisible to provide + * an opportunity for the client to provide new content. + */ + public interface OnPrepareListener { + void onPrepare(); + void onUnprepare(); + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index e77b42060f87..4901823be46e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -113,7 +113,7 @@ public class StatusBarWindowView extends FrameLayout handled = super.onTouchEvent(ev); } final int action = ev.getAction(); - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (!handled && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) { mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); } return handled; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java index dca5e41b59cf..6eb88be48569 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java @@ -22,7 +22,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Log; +import android.util.Slog; import android.view.MotionEvent; import android.view.View; @@ -75,7 +75,7 @@ public class DeadZone extends View { mVertical = (index == VERTICAL); if (DEBUG) - Log.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold + Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold + (mVertical ? " vertical" : " horizontal")); setFlashOnTouchCapture(context.getResources().getBoolean(R.bool.config_dead_zone_flash)); @@ -106,7 +106,7 @@ public class DeadZone extends View { @Override public boolean onTouchEvent(MotionEvent event) { if (DEBUG) { - Log.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); + Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); } final int action = event.getAction(); @@ -114,12 +114,12 @@ public class DeadZone extends View { poke(event); } else if (action == MotionEvent.ACTION_DOWN) { if (DEBUG) { - Log.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); + Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); } int size = (int) getSize(event.getEventTime()); if ((mVertical && event.getX() < size) || event.getY() < size) { if (CHATTY) { - Log.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")"); + Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")"); } if (mShouldFlash) { post(mDebugFlash); @@ -134,7 +134,7 @@ public class DeadZone extends View { public void poke(MotionEvent event) { mLastPokeTime = event.getEventTime(); if (DEBUG) - Log.v(TAG, "poked! size=" + getSize(mLastPokeTime)); + Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime)); if (mShouldFlash) postInvalidate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 55fb95d79da5..718acc3c4269 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -36,6 +36,7 @@ import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; @@ -53,10 +54,13 @@ public class KeyButtonView extends ImageView { int mTouchSlop; Drawable mGlowBG; int mGlowWidth, mGlowHeight; - float mGlowAlpha = 0f, mGlowScale = 1f, mDrawingAlpha = 1f; + float mGlowAlpha = 0f, mGlowScale = 1f; + @ViewDebug.ExportedProperty(category = "drawing") + float mDrawingAlpha = 1f; + @ViewDebug.ExportedProperty(category = "drawing") float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA; boolean mSupportsLongpress = true; - RectF mRect = new RectF(0f,0f,0f,0f); + RectF mRect = new RectF(); AnimatorSet mPressedAnim; Animator mAnimateToQuiescent = new ObjectAnimator(); @@ -90,8 +94,8 @@ public class KeyButtonView extends ImageView { mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true); mGlowBG = a.getDrawable(R.styleable.KeyButtonView_glowBackground); + setDrawingAlpha(mQuiescentAlpha); if (mGlowBG != null) { - setDrawingAlpha(mQuiescentAlpha); mGlowWidth = mGlowBG.getIntrinsicWidth(); mGlowHeight = mGlowBG.getIntrinsicHeight(); } @@ -126,16 +130,14 @@ public class KeyButtonView extends ImageView { public void setQuiescentAlpha(float alpha, boolean animate) { mAnimateToQuiescent.cancel(); alpha = Math.min(Math.max(alpha, 0), 1); - if (alpha == mQuiescentAlpha) return; + if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return; mQuiescentAlpha = alpha; if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha); - if (mGlowBG != null) { - if (animate) { - mAnimateToQuiescent = animateToQuiescent(); - mAnimateToQuiescent.start(); - } else { - setDrawingAlpha(mQuiescentAlpha); - } + if (mGlowBG != null && animate) { + mAnimateToQuiescent = animateToQuiescent(); + mAnimateToQuiescent.start(); + } else { + setDrawingAlpha(mQuiescentAlpha); } } @@ -143,13 +145,15 @@ public class KeyButtonView extends ImageView { return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha); } + public float getQuiescentAlpha() { + return mQuiescentAlpha; + } + public float getDrawingAlpha() { - if (mGlowBG == null) return 0; return mDrawingAlpha; } public void setDrawingAlpha(float x) { - if (mGlowBG == null) return; // Calling setAlpha(int), which is an ImageView-specific // method that's different from setAlpha(float). This sets // the alpha on this ImageView's drawable directly diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index a53b25a5d9d6..dd13e314c249 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -89,10 +89,6 @@ public class TvStatusBar extends BaseStatusBar { } @Override // CommandQueue - public void setNavigationIconHints(int hints) { - } - - @Override // CommandQueue public void setWindowState(int window, int state) { } diff --git a/packages/WallpaperCropper/res/layout/actionbar_set_wallpaper.xml b/packages/WallpaperCropper/res/layout/actionbar_set_wallpaper.xml index 1622742a52a2..2a0188ae2b6a 100644 --- a/packages/WallpaperCropper/res/layout/actionbar_set_wallpaper.xml +++ b/packages/WallpaperCropper/res/layout/actionbar_set_wallpaper.xml @@ -20,6 +20,7 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" style="?android:actionButtonStyle" + android:id="@+id/set_wallpaper_button" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView style="?android:actionBarTabTextStyle" diff --git a/packages/WallpaperCropper/res/values/config.xml b/packages/WallpaperCropper/res/values/config.xml index 1b2419073502..2e2fa6fd250a 100644 --- a/packages/WallpaperCropper/res/values/config.xml +++ b/packages/WallpaperCropper/res/values/config.xml @@ -15,4 +15,7 @@ --> <resources> <bool name="allow_rotation">false</bool> + <!-- Specifies whether to expand the cropped area on both sides (rather + than just to one side) --> + <bool name="center_crop">true</bool> </resources> diff --git a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java index e14f89a89ed8..8511de2da91a 100644 --- a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java +++ b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java @@ -24,6 +24,9 @@ import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.net.Uri; import android.os.Build; @@ -53,7 +56,8 @@ class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder { private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) { mDecoder = decoder; } - public static SimpleBitmapRegionDecoderWrapper newInstance(String pathName, boolean isShareable) { + public static SimpleBitmapRegionDecoderWrapper newInstance( + String pathName, boolean isShareable) { try { BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(pathName, isShareable); if (d != null) { @@ -65,7 +69,8 @@ class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder { } return null; } - public static SimpleBitmapRegionDecoderWrapper newInstance(InputStream is, boolean isShareable) { + public static SimpleBitmapRegionDecoderWrapper newInstance( + InputStream is, boolean isShareable) { try { BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(is, isShareable); if (d != null) { @@ -89,8 +94,9 @@ class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder { } class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder { - //byte[] streamCopy; Bitmap mBuffer; + Canvas mTempCanvas; + Paint mTempPaint; private DumbBitmapRegionDecoder(Bitmap b) { mBuffer = b; } @@ -115,9 +121,23 @@ class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder { return mBuffer.getHeight(); } public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) { - System.out.println("DECODING WITH SAMPLE LEVEL OF " + options.inSampleSize); - return Bitmap.createBitmap( - mBuffer, wantRegion.left, wantRegion.top, wantRegion.width(), wantRegion.height()); + if (mTempCanvas == null) { + mTempCanvas = new Canvas(); + mTempPaint = new Paint(); + mTempPaint.setFilterBitmap(true); + } + int sampleSize = Math.max(options.inSampleSize, 1); + Bitmap newBitmap = Bitmap.createBitmap( + wantRegion.width() / sampleSize, + wantRegion.height() / sampleSize, + Bitmap.Config.ARGB_8888); + mTempCanvas.setBitmap(newBitmap); + mTempCanvas.save(); + mTempCanvas.scale(1f / sampleSize, 1f / sampleSize); + mTempCanvas.drawBitmap(mBuffer, -wantRegion.left, -wantRegion.top, mTempPaint); + mTempCanvas.restore(); + mTempCanvas.setBitmap(null); + return newBitmap; } } @@ -256,6 +276,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { if (regionDecoder == null) { is = regenerateInputStream(); regionDecoder = DumbBitmapRegionDecoder.newInstance(is); + Utils.closeSilently(is); } return regionDecoder; } catch (FileNotFoundException e) { @@ -280,8 +301,9 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { } @Override public boolean readExif(ExifInterface ei) { + InputStream is = null; try { - InputStream is = regenerateInputStream(); + is = regenerateInputStream(); ei.readExif(is); Utils.closeSilently(is); return true; @@ -291,6 +313,8 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { } catch (IOException e) { Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e); return false; + } finally { + Utils.closeSilently(is); } } } @@ -316,6 +340,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { if (regionDecoder == null) { is = regenerateInputStream(); regionDecoder = DumbBitmapRegionDecoder.newInstance(is); + Utils.closeSilently(is); } return regionDecoder; } diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java index 14f7c1d1f7cf..c3ef3024f484 100644 --- a/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java +++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/CropView.java @@ -165,7 +165,8 @@ public class CropView extends TiledImageView implements OnScaleGestureListener { final float imageWidth = imageDims[0]; final float imageHeight = imageDims[1]; mMinScale = Math.max(w / imageWidth, h / imageHeight); - mRenderer.scale = Math.max(mMinScale, mRenderer.scale); + mRenderer.scale = + Math.max(mMinScale, resetScale ? Float.MIN_VALUE : mRenderer.scale); } } } diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java index d12140d8b1ef..757a7a2f0e5c 100644 --- a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java +++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java @@ -73,6 +73,7 @@ public class WallpaperCropActivity extends Activity { protected CropView mCropView; protected Uri mUri; + private View mSetWallpaperButton; @Override protected void onCreate(Bundle savedInstanceState) { @@ -109,10 +110,12 @@ public class WallpaperCropActivity extends Activity { cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone); } }); + mSetWallpaperButton = findViewById(R.id.set_wallpaper_button); // Load image in background final BitmapRegionTileSource.UriBitmapSource bitmapSource = new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024); + mSetWallpaperButton.setVisibility(View.INVISIBLE); Runnable onLoad = new Runnable() { public void run() { if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) { @@ -120,6 +123,8 @@ public class WallpaperCropActivity extends Activity { getString(R.string.wallpaper_load_fail), Toast.LENGTH_LONG).show(); finish(); + } else { + mSetWallpaperButton.setVisibility(View.VISIBLE); } } }; @@ -247,19 +252,19 @@ public class WallpaperCropActivity extends Activity { private static int getRotationFromExifHelper( String path, Resources res, int resId, Context context, Uri uri) { ExifInterface ei = new ExifInterface(); + InputStream is = null; + BufferedInputStream bis = null; try { if (path != null) { ei.readExif(path); } else if (uri != null) { - InputStream is = context.getContentResolver().openInputStream(uri); - BufferedInputStream bis = new BufferedInputStream(is); + is = context.getContentResolver().openInputStream(uri); + bis = new BufferedInputStream(is); ei.readExif(bis); - bis.close(); } else { - InputStream is = res.openRawResource(resId); - BufferedInputStream bis = new BufferedInputStream(is); + is = res.openRawResource(resId); + bis = new BufferedInputStream(is); ei.readExif(bis); - bis.close(); } Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION); if (ori != null) { @@ -267,6 +272,9 @@ public class WallpaperCropActivity extends Activity { } } catch (IOException e) { Log.w(LOGTAG, "Getting exif data failed", e); + } finally { + Utils.closeSilently(bis); + Utils.closeSilently(is); } return 0; } @@ -323,49 +331,32 @@ public class WallpaperCropActivity extends Activity { protected void cropImageAndSetWallpaper(Uri uri, OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) { + boolean centerCrop = getResources().getBoolean(R.bool.center_crop); // Get the crop boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; - Point minDims = new Point(); - Point maxDims = new Point(); Display d = getWindowManager().getDefaultDisplay(); - d.getCurrentSizeRange(minDims, maxDims); Point displaySize = new Point(); d.getSize(displaySize); - - int maxDim = Math.max(maxDims.x, maxDims.y); - final int minDim = Math.min(minDims.x, minDims.y); - int defaultWallpaperWidth; - if (isScreenLarge(getResources())) { - defaultWallpaperWidth = (int) (maxDim * - wallpaperTravelToScreenWidthRatio(maxDim, minDim)); - } else { - defaultWallpaperWidth = Math.max((int) - (minDim * WALLPAPER_SCREENS_SPAN), maxDim); - } - boolean isPortrait = displaySize.x < displaySize.y; - int portraitHeight; - if (isPortrait) { - portraitHeight = mCropView.getHeight(); - } else { - // TODO: how to actually get the proper portrait height? - // This is not quite right: - portraitHeight = Math.max(maxDims.x, maxDims.y); - } - if (android.os.Build.VERSION.SDK_INT >= - android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { - Point realSize = new Point(); - d.getRealSize(realSize); - portraitHeight = Math.max(realSize.x, realSize.y); - } + + Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(), + getWindowManager()); // Get the crop RectF cropRect = mCropView.getCrop(); + + Point inSize = mCropView.getSourceDimensions(); + + // Due to rounding errors in the cropview renderer the edges can be slightly offset + // therefore we ensure that the boundaries are sanely defined + cropRect.left = Math.max(0, cropRect.left); + cropRect.right = Math.min(inSize.x, cropRect.right); + cropRect.top = Math.max(0, cropRect.top); + cropRect.bottom = Math.min(inSize.y, cropRect.bottom); int cropRotation = mCropView.getImageRotation(); float cropScale = mCropView.getWidth() / (float) cropRect.width(); - Point inSize = mCropView.getSourceDimensions(); Matrix rotateMatrix = new Matrix(); rotateMatrix.setRotate(cropRotation); float[] rotatedInSize = new float[] { inSize.x, inSize.y }; @@ -376,23 +367,33 @@ public class WallpaperCropActivity extends Activity { // ADJUST CROP WIDTH // Extend the crop all the way to the right, for parallax // (or all the way to the left, in RTL) - float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left; + float extraSpace; + if (centerCrop) { + extraSpace = 2f * Math.min(rotatedInSize[0] - cropRect.right, cropRect.left); + } else { + extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left; + } // Cap the amount of extra width - float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width(); + float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width(); extraSpace = Math.min(extraSpace, maxExtraSpace); - if (ltr) { - cropRect.right += extraSpace; + if (centerCrop) { + cropRect.left -= extraSpace / 2f; + cropRect.right += extraSpace / 2f; } else { - cropRect.left -= extraSpace; + if (ltr) { + cropRect.right += extraSpace; + } else { + cropRect.left -= extraSpace; + } } // ADJUST CROP HEIGHT if (isPortrait) { - cropRect.bottom = cropRect.top + portraitHeight / cropScale; + cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale; } else { // LANDSCAPE float extraPortraitHeight = - portraitHeight / cropScale - cropRect.height(); + defaultWallpaperSize.y / cropScale - cropRect.height(); float expandHeight = Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top), extraPortraitHeight / 2); @@ -572,6 +573,8 @@ public class WallpaperCropActivity extends Activity { Rect roundedTrueCrop = new Rect(); Matrix rotateMatrix = new Matrix(); Matrix inverseRotateMatrix = new Matrix(); + + Point bounds = getImageBounds(); if (mRotation > 0) { rotateMatrix.setRotate(mRotation); inverseRotateMatrix.setRotate(-mRotation); @@ -579,7 +582,6 @@ public class WallpaperCropActivity extends Activity { mCropBounds.roundOut(roundedTrueCrop); mCropBounds = new RectF(roundedTrueCrop); - Point bounds = getImageBounds(); if (bounds == null) { Log.w(LOGTAG, "cannot get bounds for image"); failure = true; @@ -606,13 +608,13 @@ public class WallpaperCropActivity extends Activity { } // See how much we're reducing the size of the image - int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth, - roundedTrueCrop.height() / mOutHeight); - + int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth, + roundedTrueCrop.height() / mOutHeight)); // Attempt to open a region decoder BitmapRegionDecoder decoder = null; + InputStream is = null; try { - InputStream is = regenerateInputStream(); + is = regenerateInputStream(); if (is == null) { Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString()); failure = true; @@ -622,6 +624,9 @@ public class WallpaperCropActivity extends Activity { Utils.closeSilently(is); } catch (IOException e) { Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); + } finally { + Utils.closeSilently(is); + is = null; } Bitmap crop = null; @@ -637,7 +642,7 @@ public class WallpaperCropActivity extends Activity { if (crop == null) { // BitmapRegionDecoder has failed, try to crop in-memory - InputStream is = regenerateInputStream(); + is = regenerateInputStream(); Bitmap fullSize = null; if (is != null) { BitmapFactory.Options options = new BitmapFactory.Options(); @@ -648,12 +653,38 @@ public class WallpaperCropActivity extends Activity { Utils.closeSilently(is); } if (fullSize != null) { + // Find out the true sample size that was used by the decoder + scaleDownSampleSize = bounds.x / fullSize.getWidth(); mCropBounds.left /= scaleDownSampleSize; mCropBounds.top /= scaleDownSampleSize; mCropBounds.bottom /= scaleDownSampleSize; mCropBounds.right /= scaleDownSampleSize; mCropBounds.roundOut(roundedTrueCrop); + // Adjust values to account for issues related to rounding + if (roundedTrueCrop.width() > fullSize.getWidth()) { + // Adjust the width + roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth(); + } + if (roundedTrueCrop.right > fullSize.getWidth()) { + // Adjust the left value + int adjustment = roundedTrueCrop.left - + Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width()); + roundedTrueCrop.left -= adjustment; + roundedTrueCrop.right -= adjustment; + } + if (roundedTrueCrop.height() > fullSize.getHeight()) { + // Adjust the height + roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight(); + } + if (roundedTrueCrop.bottom > fullSize.getHeight()) { + // Adjust the top value + int adjustment = roundedTrueCrop.top - + Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height()); + roundedTrueCrop.top -= adjustment; + roundedTrueCrop.bottom -= adjustment; + } + crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, roundedTrueCrop.top, roundedTrueCrop.width(), roundedTrueCrop.height()); @@ -757,7 +788,7 @@ public class WallpaperCropActivity extends Activity { protected void updateWallpaperDimensions(int width, int height) { String spKey = getSharedPreferencesKey(); - SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); + SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS); SharedPreferences.Editor editor = sp.edit(); if (width != 0 && height != 0) { editor.putInt(WALLPAPER_WIDTH_KEY, width); @@ -778,14 +809,15 @@ public class WallpaperCropActivity extends Activity { final WallpaperManager wallpaperManager) { final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager); - new Thread("suggestWallpaperDimension") { - public void run() { + new AsyncTask<Void, Void, Void>() { + public Void doInBackground(Void ... args) { // If we have saved a wallpaper width/height, use that instead int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x); int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y); wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight); + return null; } - }.start(); + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } protected static RectF getMaxCropRect( diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java index 596435a350e1..10bcdad04030 100644 --- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java @@ -117,8 +117,8 @@ public class ProxyServer extends Thread { if (!proxy.equals(Proxy.NO_PROXY)) { // Only Inets created by PacProxySelector. InetSocketAddress inetSocketAddress = - (InetSocketAddress)list.get(0).address(); - server = new Socket(inetSocketAddress.getAddress(), + (InetSocketAddress)proxy.address(); + server = new Socket(inetSocketAddress.getHostName(), inetSocketAddress.getPort()); sendLine(server, requestLine); } else { diff --git a/policy/src/com/android/internal/policy/impl/BarController.java b/policy/src/com/android/internal/policy/impl/BarController.java index 8d97fc8eb028..0ce4b12529e5 100644 --- a/policy/src/com/android/internal/policy/impl/BarController.java +++ b/policy/src/com/android/internal/policy/impl/BarController.java @@ -37,8 +37,9 @@ public class BarController { private static final boolean DEBUG = false; private static final int TRANSIENT_BAR_NONE = 0; - private static final int TRANSIENT_BAR_SHOWING = 1; - private static final int TRANSIENT_BAR_HIDING = 2; + private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1; + private static final int TRANSIENT_BAR_SHOWING = 2; + private static final int TRANSIENT_BAR_HIDING = 3; private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000; @@ -73,13 +74,9 @@ public class BarController { mWin = win; } - public boolean isHidden() { - return mState == StatusBarManager.WINDOW_STATE_HIDDEN; - } - public void showTransient() { if (mWin != null) { - setTransientBarState(TRANSIENT_BAR_SHOWING); + setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED); } } @@ -87,6 +84,10 @@ public class BarController { return mTransientBarState == TRANSIENT_BAR_SHOWING; } + public boolean isTransientShowRequested() { + return mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED; + } + public boolean wasRecentlyTranslucent() { return (SystemClock.uptimeMillis() - mLastTranslucent) < TRANSLUCENT_ANIMATION_DELAY_MS; } @@ -129,8 +130,8 @@ public class BarController { final boolean wasAnim = mWin.isAnimatingLw(); final boolean change = show ? mWin.showLw(true) : mWin.hideLw(true); final int state = computeStateLw(wasVis, wasAnim, mWin, change); - updateStateLw(state); - return change; + final boolean stateChanged = updateStateLw(state); + return change || stateChanged; } private int computeStateLw(boolean wasVis, boolean wasAnim, WindowState win, boolean change) { @@ -139,6 +140,8 @@ public class BarController { final boolean anim = win.isAnimatingLw(); if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) { return StatusBarManager.WINDOW_STATE_HIDDEN; + } else if (mState == StatusBarManager.WINDOW_STATE_HIDDEN && vis) { + return StatusBarManager.WINDOW_STATE_SHOWING; } else if (change) { if (wasVis && vis && !wasAnim && anim) { return StatusBarManager.WINDOW_STATE_HIDING; @@ -150,7 +153,7 @@ public class BarController { return mState; } - private void updateStateLw(final int state) { + private boolean updateStateLw(final int state) { if (state != mState) { mState = state; if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state)); @@ -169,7 +172,9 @@ public class BarController { } } }); + return true; } + return false; } public boolean checkHiddenLw() { @@ -194,6 +199,9 @@ public class BarController { if (mTransientBarState == TRANSIENT_BAR_SHOWING) { if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown"); return false; + } else if (mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED) { + if (DEBUG) Slog.d(mTag, "Not showing transient bar, already requested"); + return false; } else if (mWin == null) { if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist"); return false; @@ -207,12 +215,13 @@ public class BarController { public int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) { if (mWin == null) return vis; - if (mTransientBarState == TRANSIENT_BAR_SHOWING) { // transient bar requested + if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested if (transientAllowed) { vis |= mTransientFlag; if ((oldVis & mTransientFlag) == 0) { vis |= mUnhideFlag; // tell sysui we're ready to unhide } + setTransientBarState(TRANSIENT_BAR_SHOWING); // request accepted } else { setTransientBarState(TRANSIENT_BAR_NONE); // request denied } @@ -250,6 +259,7 @@ public class BarController { private static String transientBarStateToString(int state) { if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING"; if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING"; + if (state == TRANSIENT_BAR_SHOW_REQUESTED) return "TRANSIENT_BAR_SHOW_REQUESTED"; if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE"; throw new IllegalArgumentException("Unknown state " + state); } diff --git a/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java b/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java index 3e57a77807c4..b734c41c9a11 100644 --- a/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java +++ b/policy/src/com/android/internal/policy/impl/ImmersiveModeConfirmation.java @@ -178,6 +178,7 @@ public class ImmersiveModeConfirmation { | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED , PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; lp.setTitle("ImmersiveModeConfirmation"); lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications; lp.gravity = Gravity.FILL; diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index faca94922a8b..b28cef57b130 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -381,6 +381,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final Rect mTmpNavigationFrame = new Rect(); WindowState mTopFullscreenOpaqueWindowState; + boolean mHideWindowBehindKeyguard; boolean mTopIsFullscreen; boolean mForceStatusBar; boolean mForceStatusBarFromKeyguard; @@ -3277,8 +3278,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { + mRestrictedScreenWidth; pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop + mRestrictedScreenHeight; - } else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT) { - // Toasts are stable to interim decor changes. + } else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT + || attrs.type == TYPE_VOLUME_OVERLAY) { + // These dialogs are stable to interim decor changes. pf.left = df.left = of.left = cf.left = mStableLeft; pf.top = df.top = of.top = cf.top = mStableTop; pf.right = df.right = of.right = cf.right = mStableRight; @@ -3364,6 +3366,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) { mTopFullscreenOpaqueWindowState = null; + mHideWindowBehindKeyguard = false; mForceStatusBar = false; mForceStatusBarFromKeyguard = false; mForcingShowNavBar = false; @@ -3382,13 +3385,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowManager.LayoutParams attrs) { if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": isVisibleOrBehindKeyguardLw=" + win.isVisibleOrBehindKeyguardLw()); - if (mTopFullscreenOpaqueWindowState == null && (win.getAttrs().privateFlags - &WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_NAV_BAR) != 0 - || (win.isVisibleLw() && attrs.type == TYPE_INPUT_METHOD)) { - if (mForcingShowNavBarLayer < 0) { - mForcingShowNavBar = true; - mForcingShowNavBarLayer = win.getSurfaceLayer(); - } + if (mTopFullscreenOpaqueWindowState == null + && win.isVisibleLw() && attrs.type == TYPE_INPUT_METHOD) { + mForcingShowNavBar = true; + mForcingShowNavBarLayer = win.getSurfaceLayer(); } if (mTopFullscreenOpaqueWindowState == null && win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw()) { @@ -3402,7 +3402,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (attrs.type == TYPE_KEYGUARD) { mShowingLockscreen = true; } - boolean applyWindow = attrs.type >= FIRST_APPLICATION_WINDOW + boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW && attrs.type <= LAST_APPLICATION_WINDOW; if (attrs.type == TYPE_DREAM) { // If the lockscreen was showing when the dream started then wait @@ -3410,30 +3410,39 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!mDreamingLockscreen || (win.isVisibleLw() && win.hasDrawnLw())) { mShowingDream = true; - applyWindow = true; + appWindow = true; } } - if (applyWindow - && attrs.x == 0 && attrs.y == 0 - && attrs.width == WindowManager.LayoutParams.MATCH_PARENT - && attrs.height == WindowManager.LayoutParams.MATCH_PARENT) { - if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win); - mTopFullscreenOpaqueWindowState = win; - if ((attrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0) { - if (DEBUG_LAYOUT) Slog.v(TAG, "Setting mHideLockScreen to true by win " + win); - mHideLockScreen = true; - mForceStatusBarFromKeyguard = false; - } - if ((attrs.flags & FLAG_DISMISS_KEYGUARD) != 0 - && mDismissKeyguard == DISMISS_KEYGUARD_NONE) { - if (DEBUG_LAYOUT) Slog.v(TAG, "Setting mDismissKeyguard true by win " + win); - mDismissKeyguard = mWinDismissingKeyguard == win ? - DISMISS_KEYGUARD_CONTINUE : DISMISS_KEYGUARD_START; - mWinDismissingKeyguard = win; - mForceStatusBarFromKeyguard = mShowingLockscreen && isKeyguardSecure(); - } - if ((attrs.flags & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) { - mAllowLockscreenWhenOn = true; + + final boolean showWhenLocked = (attrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0; + if (appWindow) { + if (attrs.x == 0 && attrs.y == 0 + && attrs.width == WindowManager.LayoutParams.MATCH_PARENT + && attrs.height == WindowManager.LayoutParams.MATCH_PARENT) { + if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win); + mTopFullscreenOpaqueWindowState = win; + if (!mHideWindowBehindKeyguard) { + if (showWhenLocked) { + if (DEBUG_LAYOUT) Slog.v(TAG, + "Setting mHideLockScreen to true by win " + win); + mHideLockScreen = true; + mForceStatusBarFromKeyguard = false; + } + if ((attrs.flags & FLAG_DISMISS_KEYGUARD) != 0 + && mDismissKeyguard == DISMISS_KEYGUARD_NONE) { + if (DEBUG_LAYOUT) Slog.v(TAG, + "Setting mDismissKeyguard true by win " + win); + mDismissKeyguard = mWinDismissingKeyguard == win ? + DISMISS_KEYGUARD_CONTINUE : DISMISS_KEYGUARD_START; + mWinDismissingKeyguard = win; + mForceStatusBarFromKeyguard = mShowingLockscreen && isKeyguardSecure(); + } + } + if ((attrs.flags & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) { + mAllowLockscreenWhenOn = true; + } + } else if (!showWhenLocked) { + mHideWindowBehindKeyguard = true; } } } @@ -3468,6 +3477,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // Maintain fullscreen layout until incoming animation is complete. topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw(); + // Transient status bar on the lockscreen is not allowed + if (mForceStatusBarFromKeyguard && mStatusBarController.isTransientShowing()) { + mStatusBarController.updateVisibilityLw(false /*transientAllowed*/, + mLastSystemUiFlags, mLastSystemUiFlags); + } } else if (mTopFullscreenOpaqueWindowState != null) { if (localLOGV) { Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw() @@ -3514,7 +3528,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mKeyguard != null) { if (localLOGV) Slog.v(TAG, "finishPostLayoutPolicyLw: mHideKeyguard=" + mHideLockScreen); - if (mDismissKeyguard != DISMISS_KEYGUARD_NONE && !mKeyguardDelegate.isSecure()) { + if (mDismissKeyguard != DISMISS_KEYGUARD_NONE && !isKeyguardSecure()) { if (mKeyguard.hideLw(true)) { changes |= FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG @@ -5158,9 +5172,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mNavigationBar != null && hideNavBarSysui && immersiveSticky; - boolean denyTransientStatus = mStatusBarController.isTransientShowing() + boolean denyTransientStatus = mStatusBarController.isTransientShowRequested() && !transientStatusBarAllowed && hideStatusBarSysui; - boolean denyTransientNav = mNavigationBarController.isTransientShowing() + boolean denyTransientNav = mNavigationBarController.isTransientShowRequested() && !transientNavBarAllowed; if (denyTransientStatus || denyTransientNav) { // clear the clearable flags instead diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java index bf22e2f79085..13574624ae26 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java @@ -40,6 +40,14 @@ public class KeyguardServiceDelegate { private KeyguardState mKeyguardState = new KeyguardState(); /* package */ static final class KeyguardState { + KeyguardState() { + // Assume keyguard is showing and secure until we know for sure. This is here in + // the event something checks before the service is actually started. + // KeyguardService itself should default to this state until the real state is known. + showing = true; + showingAndNotHidden = true; + secure = true; + } boolean showing; boolean showingAndNotHidden; boolean inputRestricted; diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index a04ee144da29..6d65a7022393 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -46,6 +46,8 @@ import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; import android.database.ContentObserver; @@ -80,7 +82,7 @@ import android.util.StringBuilderPrinter; import com.android.internal.backup.BackupConstants; import com.android.internal.backup.IBackupTransport; import com.android.internal.backup.IObbBackupService; -import com.android.internal.backup.LocalTransport; +import com.android.server.EventLogTags; import com.android.server.PackageManagerBackupAgent.Metadata; import java.io.BufferedInputStream; @@ -138,14 +140,20 @@ class BackupManagerService extends IBackupManager.Stub { private static final boolean DEBUG = true; private static final boolean MORE_DEBUG = false; + // Historical and current algorithm names + static final String PBKDF_CURRENT = "PBKDF2WithHmacSHA1"; + static final String PBKDF_FALLBACK = "PBKDF2WithHmacSHA1And8bit"; + // Name and current contents version of the full-backup manifest file static final String BACKUP_MANIFEST_FILENAME = "_manifest"; static final int BACKUP_MANIFEST_VERSION = 1; static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n"; - static final int BACKUP_FILE_VERSION = 1; + static final int BACKUP_FILE_VERSION = 2; + static final int BACKUP_PW_FILE_VERSION = 2; static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup"; + static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. @@ -158,6 +166,9 @@ class BackupManagerService extends IBackupManager.Stub { // the first backup pass. private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR; + // Retry interval for clear/init when the transport is unavailable + private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR; + private static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN"; private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT"; private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR"; @@ -171,6 +182,8 @@ class BackupManagerService extends IBackupManager.Stub { private static final int MSG_RESTORE_TIMEOUT = 8; private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9; private static final int MSG_RUN_FULL_RESTORE = 10; + private static final int MSG_RETRY_INIT = 11; + private static final int MSG_RETRY_CLEAR = 12; // backup task state machine tick static final int MSG_BACKUP_RESTORE_STEP = 20; @@ -251,10 +264,13 @@ class BackupManagerService extends IBackupManager.Stub { volatile boolean mClearingData; // Transport bookkeeping + final HashMap<String,String> mTransportNames + = new HashMap<String,String>(); // component name -> registration name final HashMap<String,IBackupTransport> mTransports - = new HashMap<String,IBackupTransport>(); + = new HashMap<String,IBackupTransport>(); // registration name -> binder + final ArrayList<TransportConnection> mTransportConnections + = new ArrayList<TransportConnection>(); String mCurrentTransport; - IBackupTransport mLocalTransport, mGoogleTransport; ActiveRestoreSession mActiveRestoreSession; // Watch the device provisioning operation during setup @@ -300,6 +316,7 @@ class BackupManagerService extends IBackupManager.Stub { class RestoreParams { public IBackupTransport transport; + public String dirName; public IRestoreObserver observer; public long token; public PackageInfo pkgInfo; @@ -307,9 +324,10 @@ class BackupManagerService extends IBackupManager.Stub { public boolean needFullBackup; public String[] filterSet; - RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, + RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs, long _token, PackageInfo _pkg, int _pmToken, boolean _needFullBackup) { transport = _transport; + dirName = _dirName; observer = _obs; token = _token; pkgInfo = _pkg; @@ -318,9 +336,10 @@ class BackupManagerService extends IBackupManager.Stub { filterSet = null; } - RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token, - boolean _needFullBackup) { + RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs, + long _token, boolean _needFullBackup) { transport = _transport; + dirName = _dirName; observer = _obs; token = _token; pkgInfo = null; @@ -329,9 +348,10 @@ class BackupManagerService extends IBackupManager.Stub { filterSet = null; } - RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token, - String[] _filterSet, boolean _needFullBackup) { + RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs, + long _token, String[] _filterSet, boolean _needFullBackup) { transport = _transport; + dirName = _dirName; observer = _obs; token = _token; pkgInfo = null; @@ -351,6 +371,16 @@ class BackupManagerService extends IBackupManager.Stub { } } + class ClearRetryParams { + public String transportName; + public String packageName; + + ClearRetryParams(String transport, String pkg) { + transportName = transport; + packageName = pkg; + } + } + class FullParams { public ParcelFileDescriptor fd; public final AtomicBoolean latch; @@ -425,6 +455,8 @@ class BackupManagerService extends IBackupManager.Stub { private final SecureRandom mRng = new SecureRandom(); private String mPasswordHash; private File mPasswordHashFile; + private int mPasswordVersion; + private File mPasswordVersionFile; private byte[] mPasswordSalt; // Configuration of PBKDF2 that we use for generating pw hashes and intermediate keys @@ -510,13 +542,28 @@ class BackupManagerService extends IBackupManager.Stub { // When it completes successfully, that old journal file will be // deleted. If we crash prior to that, the old journal is parsed // at next boot and the journaled requests fulfilled. + boolean staged = true; if (queue.size() > 0) { // Spin up a backup state sequence and set it running - PerformBackupTask pbt = new PerformBackupTask(transport, queue, oldJournal); - Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); - sendMessage(pbtMessage); + try { + String dirName = transport.transportDirName(); + PerformBackupTask pbt = new PerformBackupTask(transport, dirName, + queue, oldJournal); + Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); + sendMessage(pbtMessage); + } catch (RemoteException e) { + // unable to ask the transport its dir name -- transient failure, since + // the above check succeeded. Try again next time. + Slog.e(TAG, "Transport became unavailable attempting backup"); + staged = false; + } } else { Slog.v(TAG, "Backup requested but nothing pending"); + staged = false; + } + + if (!staged) { + // if we didn't actually hand off the wakelock, rewind until next time synchronized (mQueueLock) { mBackupRunning = false; } @@ -566,7 +613,7 @@ class BackupManagerService extends IBackupManager.Stub { RestoreParams params = (RestoreParams)msg.obj; Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer); PerformRestoreTask task = new PerformRestoreTask( - params.transport, params.observer, + params.transport, params.dirName, params.observer, params.token, params.pkgInfo, params.pmToken, params.needFullBackup, params.filterSet); Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task); @@ -593,6 +640,14 @@ class BackupManagerService extends IBackupManager.Stub { break; } + case MSG_RETRY_CLEAR: + { + // reenqueues if the transport remains unavailable + ClearRetryParams params = (ClearRetryParams)msg.obj; + clearBackupData(params.transportName, params.packageName); + break; + } + case MSG_RUN_INITIALIZE: { HashSet<String> queue; @@ -607,6 +662,16 @@ class BackupManagerService extends IBackupManager.Stub { break; } + case MSG_RETRY_INIT: + { + synchronized (mQueueLock) { + recordInitPendingLocked(msg.arg1 != 0, (String)msg.obj); + mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), + mRunInitIntent); + } + break; + } + case MSG_RUN_GET_RESTORE_SETS: { // Like other async operations, this is entered with the wakelock held @@ -752,6 +817,27 @@ class BackupManagerService extends IBackupManager.Stub { } mDataDir = Environment.getDownloadCacheDirectory(); + mPasswordVersion = 1; // unless we hear otherwise + mPasswordVersionFile = new File(mBaseStateDir, "pwversion"); + if (mPasswordVersionFile.exists()) { + FileInputStream fin = null; + DataInputStream in = null; + try { + fin = new FileInputStream(mPasswordVersionFile); + in = new DataInputStream(fin); + mPasswordVersion = in.readInt(); + } catch (IOException e) { + Slog.e(TAG, "Unable to read backup pw version"); + } finally { + try { + if (in != null) in.close(); + if (fin != null) fin.close(); + } catch (IOException e) { + Slog.w(TAG, "Error closing pw version files"); + } + } + } + mPasswordHashFile = new File(mBaseStateDir, "pwhash"); if (mPasswordHashFile.exists()) { FileInputStream fin = null; @@ -815,13 +901,7 @@ class BackupManagerService extends IBackupManager.Stub { } // Set up our transport options and initialize the default transport - // TODO: Have transports register themselves somehow? // TODO: Don't create transports that we don't need to? - mLocalTransport = new LocalTransport(context); // This is actually pretty cheap - ComponentName localName = new ComponentName(context, LocalTransport.class); - registerTransport(localName.flattenToShortString(), mLocalTransport); - - mGoogleTransport = null; mCurrentTransport = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT); if ("".equals(mCurrentTransport)) { @@ -829,28 +909,43 @@ class BackupManagerService extends IBackupManager.Stub { } if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport); - // Attach to the Google backup transport. When this comes up, it will set - // itself as the current transport because we explicitly reset mCurrentTransport - // to null. - ComponentName transportComponent = new ComponentName("com.google.android.backup", - "com.google.android.backup.BackupTransportService"); - try { - // If there's something out there that is supposed to be the Google - // backup transport, make sure it's legitimately part of the OS build - // and not an app lying about its package name. - ApplicationInfo info = mPackageManager.getApplicationInfo( - transportComponent.getPackageName(), 0); - if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - if (DEBUG) Slog.v(TAG, "Binding to Google transport"); - Intent intent = new Intent().setComponent(transportComponent); - context.bindServiceAsUser(intent, mGoogleConnection, Context.BIND_AUTO_CREATE, - UserHandle.OWNER); - } else { - Slog.w(TAG, "Possible Google transport spoof: ignoring " + info); + // Find transport hosts and bind to their services + Intent transportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); + List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser( + transportServiceIntent, 0, UserHandle.USER_OWNER); + if (DEBUG) { + Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size())); + } + if (hosts != null) { + if (MORE_DEBUG) { + for (int i = 0; i < hosts.size(); i++) { + ServiceInfo info = hosts.get(i).serviceInfo; + Slog.v(TAG, " " + info.packageName + "/" + info.name); + } + } + for (int i = 0; i < hosts.size(); i++) { + try { + ServiceInfo info = hosts.get(i).serviceInfo; + PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0); + if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) { + ComponentName svcName = new ComponentName(info.packageName, info.name); + if (DEBUG) { + Slog.i(TAG, "Binding to transport host " + svcName); + } + Intent intent = new Intent(transportServiceIntent); + intent.setComponent(svcName); + TransportConnection connection = new TransportConnection(); + mTransportConnections.add(connection); + context.bindServiceAsUser(intent, + connection, Context.BIND_AUTO_CREATE, + UserHandle.OWNER); + } else { + Slog.w(TAG, "Transport package not privileged: " + info.packageName); + } + } catch (Exception e) { + Slog.e(TAG, "Problem resolving transport service: " + e.getMessage()); + } } - } catch (PackageManager.NameNotFoundException nnf) { - // No such package? No binding. - if (DEBUG) Slog.v(TAG, "Google transport not present"); } // Now that we know about valid backup participants, parse any @@ -1043,13 +1138,13 @@ class BackupManagerService extends IBackupManager.Stub { } } - private SecretKey buildPasswordKey(String pw, byte[] salt, int rounds) { - return buildCharArrayKey(pw.toCharArray(), salt, rounds); + private SecretKey buildPasswordKey(String algorithm, String pw, byte[] salt, int rounds) { + return buildCharArrayKey(algorithm, pw.toCharArray(), salt, rounds); } - private SecretKey buildCharArrayKey(char[] pwArray, byte[] salt, int rounds) { + private SecretKey buildCharArrayKey(String algorithm, char[] pwArray, byte[] salt, int rounds) { try { - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm); KeySpec ks = new PBEKeySpec(pwArray, salt, rounds, PBKDF2_KEY_SIZE); return keyFactory.generateSecret(ks); } catch (InvalidKeySpecException e) { @@ -1060,8 +1155,8 @@ class BackupManagerService extends IBackupManager.Stub { return null; } - private String buildPasswordHash(String pw, byte[] salt, int rounds) { - SecretKey key = buildPasswordKey(pw, salt, rounds); + private String buildPasswordHash(String algorithm, String pw, byte[] salt, int rounds) { + SecretKey key = buildPasswordKey(algorithm, pw, salt, rounds); if (key != null) { return byteArrayToHex(key.getEncoded()); } @@ -1089,13 +1184,13 @@ class BackupManagerService extends IBackupManager.Stub { return result; } - private byte[] makeKeyChecksum(byte[] pwBytes, byte[] salt, int rounds) { + private byte[] makeKeyChecksum(String algorithm, byte[] pwBytes, byte[] salt, int rounds) { char[] mkAsChar = new char[pwBytes.length]; for (int i = 0; i < pwBytes.length; i++) { mkAsChar[i] = (char) pwBytes[i]; } - Key checksum = buildCharArrayKey(mkAsChar, salt, rounds); + Key checksum = buildCharArrayKey(algorithm, mkAsChar, salt, rounds); return checksum.getEncoded(); } @@ -1107,7 +1202,7 @@ class BackupManagerService extends IBackupManager.Stub { } // Backup password management - boolean passwordMatchesSaved(String candidatePw, int rounds) { + boolean passwordMatchesSaved(String algorithm, String candidatePw, int rounds) { // First, on an encrypted device we require matching the device pw final boolean isEncrypted; try { @@ -1150,7 +1245,7 @@ class BackupManagerService extends IBackupManager.Stub { } else { // hash the stated current pw and compare to the stored one if (candidatePw != null && candidatePw.length() > 0) { - String currentPwHash = buildPasswordHash(candidatePw, mPasswordSalt, rounds); + String currentPwHash = buildPasswordHash(algorithm, candidatePw, mPasswordSalt, rounds); if (mPasswordHash.equalsIgnoreCase(currentPwHash)) { // candidate hash matches the stored hash -- the password matches return true; @@ -1165,11 +1260,37 @@ class BackupManagerService extends IBackupManager.Stub { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "setBackupPassword"); - // If the supplied pw doesn't hash to the the saved one, fail - if (!passwordMatchesSaved(currentPw, PBKDF2_HASH_ROUNDS)) { + // When processing v1 passwords we may need to try two different PBKDF2 checksum regimes + final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION); + + // If the supplied pw doesn't hash to the the saved one, fail. The password + // might be caught in the legacy crypto mismatch; verify that too. + if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS) + && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK, + currentPw, PBKDF2_HASH_ROUNDS))) { return false; } + // Snap up to current on the pw file version + mPasswordVersion = BACKUP_PW_FILE_VERSION; + FileOutputStream pwFout = null; + DataOutputStream pwOut = null; + try { + pwFout = new FileOutputStream(mPasswordVersionFile); + pwOut = new DataOutputStream(pwFout); + pwOut.writeInt(mPasswordVersion); + } catch (IOException e) { + Slog.e(TAG, "Unable to write backup pw version; password not changed"); + return false; + } finally { + try { + if (pwOut != null) pwOut.close(); + if (pwFout != null) pwFout.close(); + } catch (IOException e) { + Slog.w(TAG, "Unable to close pw version record"); + } + } + // Clearing the password is okay if (newPw == null || newPw.isEmpty()) { if (mPasswordHashFile.exists()) { @@ -1187,7 +1308,7 @@ class BackupManagerService extends IBackupManager.Stub { try { // Okay, build the hash of the new backup password byte[] salt = randomBytes(PBKDF2_SALT_SIZE); - String newPwHash = buildPasswordHash(newPw, salt, PBKDF2_HASH_ROUNDS); + String newPwHash = buildPasswordHash(PBKDF_CURRENT, newPw, salt, PBKDF2_HASH_ROUNDS); OutputStream pwf = null, buffer = null; DataOutputStream out = null; @@ -1230,34 +1351,65 @@ class BackupManagerService extends IBackupManager.Stub { } } + private boolean backupPasswordMatches(String currentPw) { + if (hasBackupPassword()) { + final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION); + if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS) + && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK, + currentPw, PBKDF2_HASH_ROUNDS))) { + if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting"); + return false; + } + } + return true; + } + // Maintain persistent state around whether need to do an initialize operation. // Must be called with the queue lock held. void recordInitPendingLocked(boolean isPending, String transportName) { if (DEBUG) Slog.i(TAG, "recordInitPendingLocked: " + isPending + " on transport " + transportName); + mBackupHandler.removeMessages(MSG_RETRY_INIT); + try { IBackupTransport transport = getTransport(transportName); - String transportDirName = transport.transportDirName(); - File stateDir = new File(mBaseStateDir, transportDirName); - File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME); - - if (isPending) { - // We need an init before we can proceed with sending backup data. - // Record that with an entry in our set of pending inits, as well as - // journaling it via creation of a sentinel file. - mPendingInits.add(transportName); - try { - (new FileOutputStream(initPendingFile)).close(); - } catch (IOException ioe) { - // Something is badly wrong with our permissions; just try to move on + if (transport != null) { + String transportDirName = transport.transportDirName(); + File stateDir = new File(mBaseStateDir, transportDirName); + File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME); + + if (isPending) { + // We need an init before we can proceed with sending backup data. + // Record that with an entry in our set of pending inits, as well as + // journaling it via creation of a sentinel file. + mPendingInits.add(transportName); + try { + (new FileOutputStream(initPendingFile)).close(); + } catch (IOException ioe) { + // Something is badly wrong with our permissions; just try to move on + } + } else { + // No more initialization needed; wipe the journal and reset our state. + initPendingFile.delete(); + mPendingInits.remove(transportName); } - } else { - // No more initialization needed; wipe the journal and reset our state. - initPendingFile.delete(); - mPendingInits.remove(transportName); + return; // done; don't fall through to the error case } } catch (RemoteException e) { - // can't happen; the transport is local + // transport threw when asked its name; fall through to the lookup-failed case + } + + // The named transport doesn't exist or threw. This operation is + // important, so we record the need for a an init and post a message + // to retry the init later. + if (isPending) { + mPendingInits.add(transportName); + mBackupHandler.sendMessageDelayed( + mBackupHandler.obtainMessage(MSG_RETRY_INIT, + (isPending ? 1 : 0), + 0, + transportName), + TRANSPORT_RETRY_INTERVAL); } } @@ -1298,13 +1450,16 @@ class BackupManagerService extends IBackupManager.Stub { // Add a transport to our set of available backends. If 'transport' is null, this // is an unregistration, and the transport's entry is removed from our bookkeeping. - private void registerTransport(String name, IBackupTransport transport) { + private void registerTransport(String name, String component, IBackupTransport transport) { synchronized (mTransports) { - if (DEBUG) Slog.v(TAG, "Registering transport " + name + " = " + transport); + if (DEBUG) Slog.v(TAG, "Registering transport " + + component + "::" + name + " = " + transport); if (transport != null) { mTransports.put(name, transport); + mTransportNames.put(component, name); } else { - mTransports.remove(name); + mTransports.remove(mTransportNames.get(component)); + mTransportNames.remove(component); // Nothing further to do in the unregistration case return; } @@ -1330,7 +1485,10 @@ class BackupManagerService extends IBackupManager.Stub { } } } catch (RemoteException e) { - // can't happen, the transport is local + // the transport threw when asked its file naming prefs; declare it invalid + Slog.e(TAG, "Unable to register transport as " + name); + mTransportNames.remove(component); + mTransports.remove(name); } } @@ -1390,18 +1548,23 @@ class BackupManagerService extends IBackupManager.Stub { } }; - // ----- Track connection to GoogleBackupTransport service ----- - ServiceConnection mGoogleConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG) Slog.v(TAG, "Connected to Google transport"); - mGoogleTransport = IBackupTransport.Stub.asInterface(service); - registerTransport(name.flattenToShortString(), mGoogleTransport); + // ----- Track connection to transports service ----- + class TransportConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName component, IBinder service) { + if (DEBUG) Slog.v(TAG, "Connected to transport " + component); + try { + IBackupTransport transport = IBackupTransport.Stub.asInterface(service); + registerTransport(transport.name(), component.flattenToShortString(), transport); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to register transport " + component); + } } - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) Slog.v(TAG, "Disconnected from Google transport"); - mGoogleTransport = null; - registerTransport(name.flattenToShortString(), null); + @Override + public void onServiceDisconnected(ComponentName component) { + if (DEBUG) Slog.v(TAG, "Disconnected from transport " + component); + registerTransport(null, component.flattenToShortString(), null); } }; @@ -1645,7 +1808,7 @@ class BackupManagerService extends IBackupManager.Stub { agent = mConnectedAgent; } } catch (RemoteException e) { - // can't happen + // can't happen - ActivityManager is local } } return agent; @@ -1821,17 +1984,13 @@ class BackupManagerService extends IBackupManager.Stub { int mStatus; boolean mFinished; - public PerformBackupTask(IBackupTransport transport, ArrayList<BackupRequest> queue, - File journal) { + public PerformBackupTask(IBackupTransport transport, String dirName, + ArrayList<BackupRequest> queue, File journal) { mTransport = transport; mOriginalQueue = queue; mJournal = journal; - try { - mStateDir = new File(mBaseStateDir, transport.transportDirName()); - } catch (RemoteException e) { - // can't happen; the transport is local - } + mStateDir = new File(mBaseStateDir, dirName); mCurrentState = BackupState.INITIAL; mFinished = false; @@ -2077,8 +2236,12 @@ class BackupManagerService extends IBackupManager.Stub { addBackupTrace("success; recording token"); try { mCurrentToken = mTransport.getCurrentRestoreSet(); - } catch (RemoteException e) {} // can't happen - writeRestoreTokens(); + writeRestoreTokens(); + } catch (RemoteException e) { + // nothing for it at this point, unfortunately, but this will be + // recorded the next time we fully succeed. + addBackupTrace("transport threw returning token"); + } } // Set up the next backup pass - at this point we can set mBackupRunning @@ -2299,7 +2462,7 @@ class BackupManagerService extends IBackupManager.Stub { addBackupTrace("unbinding " + mCurrentPackage.packageName); try { // unbind even on timeout, just in case mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo); - } catch (RemoteException e) {} + } catch (RemoteException e) { /* can't happen; activity manager is local */ } } } @@ -2621,11 +2784,9 @@ class BackupManagerService extends IBackupManager.Stub { // Verify that the given password matches the currently-active // backup password, if any - if (hasBackupPassword()) { - if (!passwordMatchesSaved(mCurrentPassword, PBKDF2_HASH_ROUNDS)) { - if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting"); - return; - } + if (!backupPasswordMatches(mCurrentPassword)) { + if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting"); + return; } // Write the global file header. All strings are UTF-8 encoded; lines end @@ -2633,7 +2794,7 @@ class BackupManagerService extends IBackupManager.Stub { // final '\n'. // // line 1: "ANDROID BACKUP" - // line 2: backup file format version, currently "1" + // line 2: backup file format version, currently "2" // line 3: compressed? "0" if not compressed, "1" if compressed. // line 4: name of encryption algorithm [currently only "none" or "AES-256"] // @@ -2741,7 +2902,7 @@ class BackupManagerService extends IBackupManager.Stub { OutputStream ofstream) throws Exception { // User key will be used to encrypt the master key. byte[] newUserSalt = randomBytes(PBKDF2_SALT_SIZE); - SecretKey userKey = buildPasswordKey(mEncryptPassword, newUserSalt, + SecretKey userKey = buildPasswordKey(PBKDF_CURRENT, mEncryptPassword, newUserSalt, PBKDF2_HASH_ROUNDS); // the master key is random for each backup @@ -2788,7 +2949,7 @@ class BackupManagerService extends IBackupManager.Stub { // stated number of PBKDF2 rounds IV = c.getIV(); byte[] mk = masterKeySpec.getEncoded(); - byte[] checksum = makeKeyChecksum(masterKeySpec.getEncoded(), + byte[] checksum = makeKeyChecksum(PBKDF_CURRENT, masterKeySpec.getEncoded(), checksumSalt, PBKDF2_HASH_ROUNDS); ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length @@ -3131,11 +3292,9 @@ class BackupManagerService extends IBackupManager.Stub { FileInputStream rawInStream = null; DataInputStream rawDataIn = null; try { - if (hasBackupPassword()) { - if (!passwordMatchesSaved(mCurrentPassword, PBKDF2_HASH_ROUNDS)) { - if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting"); - return; - } + if (!backupPasswordMatches(mCurrentPassword)) { + if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting"); + return; } mBytes = 0; @@ -3156,8 +3315,12 @@ class BackupManagerService extends IBackupManager.Stub { if (Arrays.equals(magicBytes, streamHeader)) { // okay, header looks good. now parse out the rest of the fields. String s = readHeaderLine(rawInStream); - if (Integer.parseInt(s) == BACKUP_FILE_VERSION) { - // okay, it's a version we recognize + final int archiveVersion = Integer.parseInt(s); + if (archiveVersion <= BACKUP_FILE_VERSION) { + // okay, it's a version we recognize. if it's version 1, we may need + // to try two different PBKDF2 regimes to compare checksums. + final boolean pbkdf2Fallback = (archiveVersion == 1); + s = readHeaderLine(rawInStream); compressed = (Integer.parseInt(s) != 0); s = readHeaderLine(rawInStream); @@ -3165,7 +3328,8 @@ class BackupManagerService extends IBackupManager.Stub { // no more header to parse; we're good to go okay = true; } else if (mDecryptPassword != null && mDecryptPassword.length() > 0) { - preCompressStream = decodeAesHeaderAndInitialize(s, rawInStream); + preCompressStream = decodeAesHeaderAndInitialize(s, pbkdf2Fallback, + rawInStream); if (preCompressStream != null) { okay = true; } @@ -3225,7 +3389,71 @@ class BackupManagerService extends IBackupManager.Stub { return buffer.toString(); } - InputStream decodeAesHeaderAndInitialize(String encryptionName, InputStream rawInStream) { + InputStream attemptMasterKeyDecryption(String algorithm, byte[] userSalt, byte[] ckSalt, + int rounds, String userIvHex, String masterKeyBlobHex, InputStream rawInStream, + boolean doLog) { + InputStream result = null; + + try { + Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKey userKey = buildPasswordKey(algorithm, mDecryptPassword, userSalt, + rounds); + byte[] IV = hexToByteArray(userIvHex); + IvParameterSpec ivSpec = new IvParameterSpec(IV); + c.init(Cipher.DECRYPT_MODE, + new SecretKeySpec(userKey.getEncoded(), "AES"), + ivSpec); + byte[] mkCipher = hexToByteArray(masterKeyBlobHex); + byte[] mkBlob = c.doFinal(mkCipher); + + // first, the master key IV + int offset = 0; + int len = mkBlob[offset++]; + IV = Arrays.copyOfRange(mkBlob, offset, offset + len); + offset += len; + // then the master key itself + len = mkBlob[offset++]; + byte[] mk = Arrays.copyOfRange(mkBlob, + offset, offset + len); + offset += len; + // and finally the master key checksum hash + len = mkBlob[offset++]; + byte[] mkChecksum = Arrays.copyOfRange(mkBlob, + offset, offset + len); + + // now validate the decrypted master key against the checksum + byte[] calculatedCk = makeKeyChecksum(algorithm, mk, ckSalt, rounds); + if (Arrays.equals(calculatedCk, mkChecksum)) { + ivSpec = new IvParameterSpec(IV); + c.init(Cipher.DECRYPT_MODE, + new SecretKeySpec(mk, "AES"), + ivSpec); + // Only if all of the above worked properly will 'result' be assigned + result = new CipherInputStream(rawInStream, c); + } else if (doLog) Slog.w(TAG, "Incorrect password"); + } catch (InvalidAlgorithmParameterException e) { + if (doLog) Slog.e(TAG, "Needed parameter spec unavailable!", e); + } catch (BadPaddingException e) { + // This case frequently occurs when the wrong password is used to decrypt + // the master key. Use the identical "incorrect password" log text as is + // used in the checksum failure log in order to avoid providing additional + // information to an attacker. + if (doLog) Slog.w(TAG, "Incorrect password"); + } catch (IllegalBlockSizeException e) { + if (doLog) Slog.w(TAG, "Invalid block size in master key"); + } catch (NoSuchAlgorithmException e) { + if (doLog) Slog.e(TAG, "Needed decryption algorithm unavailable!"); + } catch (NoSuchPaddingException e) { + if (doLog) Slog.e(TAG, "Needed padding mechanism unavailable!"); + } catch (InvalidKeyException e) { + if (doLog) Slog.w(TAG, "Illegal password; aborting"); + } + + return result; + } + + InputStream decodeAesHeaderAndInitialize(String encryptionName, boolean pbkdf2Fallback, + InputStream rawInStream) { InputStream result = null; try { if (encryptionName.equals(ENCRYPTION_ALGORITHM_NAME)) { @@ -3242,59 +3470,13 @@ class BackupManagerService extends IBackupManager.Stub { String masterKeyBlobHex = readHeaderLine(rawInStream); // 9 // decrypt the master key blob - Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); - SecretKey userKey = buildPasswordKey(mDecryptPassword, userSalt, - rounds); - byte[] IV = hexToByteArray(userIvHex); - IvParameterSpec ivSpec = new IvParameterSpec(IV); - c.init(Cipher.DECRYPT_MODE, - new SecretKeySpec(userKey.getEncoded(), "AES"), - ivSpec); - byte[] mkCipher = hexToByteArray(masterKeyBlobHex); - byte[] mkBlob = c.doFinal(mkCipher); - - // first, the master key IV - int offset = 0; - int len = mkBlob[offset++]; - IV = Arrays.copyOfRange(mkBlob, offset, offset + len); - offset += len; - // then the master key itself - len = mkBlob[offset++]; - byte[] mk = Arrays.copyOfRange(mkBlob, - offset, offset + len); - offset += len; - // and finally the master key checksum hash - len = mkBlob[offset++]; - byte[] mkChecksum = Arrays.copyOfRange(mkBlob, - offset, offset + len); - - // now validate the decrypted master key against the checksum - byte[] calculatedCk = makeKeyChecksum(mk, ckSalt, rounds); - if (Arrays.equals(calculatedCk, mkChecksum)) { - ivSpec = new IvParameterSpec(IV); - c.init(Cipher.DECRYPT_MODE, - new SecretKeySpec(mk, "AES"), - ivSpec); - // Only if all of the above worked properly will 'result' be assigned - result = new CipherInputStream(rawInStream, c); - } else Slog.w(TAG, "Incorrect password"); + result = attemptMasterKeyDecryption(PBKDF_CURRENT, userSalt, ckSalt, + rounds, userIvHex, masterKeyBlobHex, rawInStream, false); + if (result == null && pbkdf2Fallback) { + result = attemptMasterKeyDecryption(PBKDF_FALLBACK, userSalt, ckSalt, + rounds, userIvHex, masterKeyBlobHex, rawInStream, true); + } } else Slog.w(TAG, "Unsupported encryption method: " + encryptionName); - } catch (InvalidAlgorithmParameterException e) { - Slog.e(TAG, "Needed parameter spec unavailable!", e); - } catch (BadPaddingException e) { - // This case frequently occurs when the wrong password is used to decrypt - // the master key. Use the identical "incorrect password" log text as is - // used in the checksum failure log in order to avoid providing additional - // information to an attacker. - Slog.w(TAG, "Incorrect password"); - } catch (IllegalBlockSizeException e) { - Slog.w(TAG, "Invalid block size in master key"); - } catch (NoSuchAlgorithmException e) { - Slog.e(TAG, "Needed decryption algorithm unavailable!"); - } catch (NoSuchPaddingException e) { - Slog.e(TAG, "Needed padding mechanism unavailable!"); - } catch (InvalidKeyException e) { - Slog.w(TAG, "Illegal password; aborting"); } catch (NumberFormatException e) { Slog.w(TAG, "Can't parse restore data header"); } catch (IOException e) { @@ -4314,7 +4496,7 @@ class BackupManagerService extends IBackupManager.Stub { } } - PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer, + PerformRestoreTask(IBackupTransport transport, String dirName, IRestoreObserver observer, long restoreSetToken, PackageInfo targetPackage, int pmToken, boolean needFullBackup, String[] filterSet) { mCurrentState = RestoreState.INITIAL; @@ -4337,11 +4519,7 @@ class BackupManagerService extends IBackupManager.Stub { mFilterSet = null; } - try { - mStateDir = new File(mBaseStateDir, transport.transportDirName()); - } catch (RemoteException e) { - // can't happen; the transport is local - } + mStateDir = new File(mBaseStateDir, dirName); } // Execute one tick of whatever state machine the task implements @@ -5067,8 +5245,8 @@ class BackupManagerService extends IBackupManager.Stub { } // Clear the given package's backup data from the current transport - public void clearBackupData(String packageName) { - if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName); + public void clearBackupData(String transportName, String packageName) { + if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName); PackageInfo info; try { info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); @@ -5099,13 +5277,22 @@ class BackupManagerService extends IBackupManager.Stub { // Is the given app an available participant? if (apps.contains(packageName)) { - if (DEBUG) Slog.v(TAG, "Found the app - running clear process"); // found it; fire off the clear request + if (DEBUG) Slog.v(TAG, "Found the app - running clear process"); + mBackupHandler.removeMessages(MSG_RETRY_CLEAR); synchronized (mQueueLock) { + final IBackupTransport transport = getTransport(transportName); + if (transport == null) { + // transport is currently unavailable -- make sure to retry + Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR, + new ClearRetryParams(transportName, packageName)); + mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL); + return; + } long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR, - new ClearParams(getTransport(mCurrentTransport), info)); + new ClearParams(transport, info)); mBackupHandler.sendMessage(msg); Binder.restoreCallingIdentity(oldId); } @@ -5597,31 +5784,55 @@ class BackupManagerService extends IBackupManager.Stub { return; } + boolean skip = false; + long restoreSet = getAvailableRestoreToken(packageName); if (DEBUG) Slog.v(TAG, "restoreAtInstall pkg=" + packageName + " token=" + Integer.toHexString(token) + " restoreSet=" + Long.toHexString(restoreSet)); + if (restoreSet == 0) { + if (MORE_DEBUG) Slog.i(TAG, "No restore set"); + skip = true; + } - if (mAutoRestore && mProvisioned && restoreSet != 0) { - // okay, we're going to attempt a restore of this package from this restore set. - // The eventual message back into the Package Manager to run the post-install - // steps for 'token' will be issued from the restore handling code. + // Do we have a transport to fetch data for us? + IBackupTransport transport = getTransport(mCurrentTransport); + if (transport == null) { + if (DEBUG) Slog.w(TAG, "No transport"); + skip = true; + } - // We can use a synthetic PackageInfo here because: - // 1. We know it's valid, since the Package Manager supplied the name - // 2. Only the packageName field will be used by the restore code - PackageInfo pkg = new PackageInfo(); - pkg.packageName = packageName; + if (!skip && mAutoRestore && mProvisioned) { + try { + // okay, we're going to attempt a restore of this package from this restore set. + // The eventual message back into the Package Manager to run the post-install + // steps for 'token' will be issued from the restore handling code. - mWakelock.acquire(); - Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(getTransport(mCurrentTransport), null, - restoreSet, pkg, token, true); - mBackupHandler.sendMessage(msg); - } else { + // This can throw and so *must* happen before the wakelock is acquired + String dirName = transport.transportDirName(); + + // We can use a synthetic PackageInfo here because: + // 1. We know it's valid, since the Package Manager supplied the name + // 2. Only the packageName field will be used by the restore code + PackageInfo pkg = new PackageInfo(); + pkg.packageName = packageName; + + mWakelock.acquire(); + Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); + msg.obj = new RestoreParams(transport, dirName, null, + restoreSet, pkg, token, true); + mBackupHandler.sendMessage(msg); + } catch (RemoteException e) { + // Binding to the transport broke; back off and proceed with the installation. + Slog.e(TAG, "Unable to contact transport"); + skip = true; + } + } + + if (skip) { // Auto-restore disabled or no way to attempt a restore; just tell the Package // Manager to proceed with the post-install handling for this package. - if (DEBUG) Slog.v(TAG, "No restore set -- skipping restore"); + if (DEBUG) Slog.v(TAG, "Skipping"); try { mPackageManagerBinder.finishPackageInstall(token); } catch (RemoteException e) { /* can't happen */ } @@ -5774,13 +5985,23 @@ class BackupManagerService extends IBackupManager.Stub { return -1; } + String dirName; + try { + dirName = mRestoreTransport.transportDirName(); + } catch (RemoteException e) { + // Transport went AWOL; fail. + Slog.e(TAG, "Unable to contact transport for restore"); + return -1; + } + synchronized (mQueueLock) { for (int i = 0; i < mRestoreSets.length; i++) { if (token == mRestoreSets[i].token) { long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(mRestoreTransport, observer, token, true); + msg.obj = new RestoreParams(mRestoreTransport, dirName, + observer, token, true); mBackupHandler.sendMessage(msg); Binder.restoreCallingIdentity(oldId); return 0; @@ -5834,13 +6055,22 @@ class BackupManagerService extends IBackupManager.Stub { return -1; } + String dirName; + try { + dirName = mRestoreTransport.transportDirName(); + } catch (RemoteException e) { + // Transport went AWOL; fail. + Slog.e(TAG, "Unable to contact transport for restore"); + return -1; + } + synchronized (mQueueLock) { for (int i = 0; i < mRestoreSets.length; i++) { if (token == mRestoreSets[i].token) { long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(mRestoreTransport, observer, token, + msg.obj = new RestoreParams(mRestoreTransport, dirName, observer, token, packages, true); mBackupHandler.sendMessage(msg); Binder.restoreCallingIdentity(oldId); @@ -5906,11 +6136,21 @@ class BackupManagerService extends IBackupManager.Stub { return -1; } + String dirName; + try { + dirName = mRestoreTransport.transportDirName(); + } catch (RemoteException e) { + // Transport went AWOL; fail. + Slog.e(TAG, "Unable to contact transport for restore"); + return -1; + } + // Ready to go: enqueue the restore request and claim success long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(mRestoreTransport, observer, token, app, 0, false); + msg.obj = new RestoreParams(mRestoreTransport, dirName, + observer, token, app, 0, false); mBackupHandler.sendMessage(msg); Binder.restoreCallingIdentity(oldId); return 0; diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 7c61c4400144..41a0e6b9a61d 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -32,6 +32,7 @@ import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import android.app.AlarmManager; +import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -43,7 +44,9 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; @@ -77,6 +80,7 @@ import android.net.wifi.WifiStateTracker; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; import android.os.Binder; +import android.os.Build; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; @@ -141,6 +145,7 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.URL; +import java.net.URLConnection; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; @@ -154,6 +159,10 @@ import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; + /** * @hide */ @@ -406,6 +415,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private SettingsObserver mSettingsObserver; + private AppOpsManager mAppOpsManager; + NetworkConfig[] mNetConfigs; int mNetworksDefined; @@ -689,6 +700,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { filter = new IntentFilter(); filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION); mContext.registerReceiver(mProvisioningReceiver, filter); + + mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); } /** @@ -1521,6 +1534,40 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** + * Check if the address falls into any of currently running VPN's route's. + */ + private boolean isAddressUnderVpn(InetAddress address) { + synchronized (mVpns) { + synchronized (mRoutesLock) { + int uid = UserHandle.getCallingUserId(); + Vpn vpn = mVpns.get(uid); + if (vpn == null) { + return false; + } + + // Check if an exemption exists for this address. + for (LinkAddress destination : mExemptAddresses) { + if (!NetworkUtils.addressTypeMatches(address, destination.getAddress())) { + continue; + } + + int prefix = destination.getNetworkPrefixLength(); + InetAddress addrMasked = NetworkUtils.getNetworkPart(address, prefix); + InetAddress destMasked = NetworkUtils.getNetworkPart(destination.getAddress(), + prefix); + + if (addrMasked.equals(destMasked)) { + return false; + } + } + + // Finally check if the address is covered by the VPN. + return vpn.isAddressCovered(address); + } + } + } + + /** * @deprecated use requestRouteToHostAddress instead * * Ensure that a network route exists to deliver traffic to the specified @@ -1531,14 +1578,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { * desired * @return {@code true} on success, {@code false} on failure */ - public boolean requestRouteToHost(int networkType, int hostAddress) { + public boolean requestRouteToHost(int networkType, int hostAddress, String packageName) { InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress); if (inetAddress == null) { return false; } - return requestRouteToHostAddress(networkType, inetAddress.getAddress()); + return requestRouteToHostAddress(networkType, inetAddress.getAddress(), packageName); } /** @@ -1550,11 +1597,40 @@ public class ConnectivityService extends IConnectivityManager.Stub { * desired * @return {@code true} on success, {@code false} on failure */ - public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) { + public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress, + String packageName) { enforceChangePermission(); if (mProtectedNetworks.contains(networkType)) { enforceConnectivityInternalPermission(); } + boolean exempt; + InetAddress addr; + try { + addr = InetAddress.getByAddress(hostAddress); + } catch (UnknownHostException e) { + if (DBG) log("requestRouteToHostAddress got " + e.toString()); + return false; + } + // System apps may request routes bypassing the VPN to keep other networks working. + if (Binder.getCallingUid() == Process.SYSTEM_UID) { + exempt = true; + } else { + mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName); + try { + ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(packageName, + 0); + exempt = (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } catch (NameNotFoundException e) { + throw new IllegalArgumentException("Failed to find calling package details", e); + } + } + + // Non-exempt routeToHost's can only be added if the host is not covered by the VPN. + // This can be either because the VPN's routes do not cover the destination or a + // system application added an exemption that covers this destination. + if (!exempt && isAddressUnderVpn(addr)) { + return false; + } if (!ConnectivityManager.isNetworkTypeValid(networkType)) { if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType); @@ -1578,18 +1654,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { } final long token = Binder.clearCallingIdentity(); try { - InetAddress addr = InetAddress.getByAddress(hostAddress); LinkProperties lp = tracker.getLinkProperties(); - boolean ok = addRouteToAddress(lp, addr, EXEMPT); + boolean ok = addRouteToAddress(lp, addr, exempt); if (DBG) log("requestRouteToHostAddress ok=" + ok); return ok; - } catch (UnknownHostException e) { - if (DBG) log("requestRouteToHostAddress got " + e.toString()); } finally { Binder.restoreCallingIdentity(token); } - if (DBG) log("requestRouteToHostAddress X bottom return false"); - return false; } private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable, @@ -2299,9 +2370,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { mInetConditionChangeInFlight = false; // Don't do this - if we never sign in stay, grey //reportNetworkCondition(mActiveDefaultNetwork, 100); + updateNetworkSettings(thisNet); } thisNet.setTeardownRequested(false); - updateNetworkSettings(thisNet); updateMtuSizeSettings(thisNet); handleConnectivityChange(newNetType, false); sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay()); @@ -2686,6 +2757,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { } setBufferSize(bufferSizes); } + + final String defaultRwndKey = "net.tcp.default_init_rwnd"; + int defaultRwndValue = SystemProperties.getInt(defaultRwndKey, 0); + Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.TCP_DEFAULT_INIT_RWND, defaultRwndValue); + final String sysctlKey = "sys.sysctl.tcp_def_init_rwnd"; + if (rwndValue != 0) { + SystemProperties.set(sysctlKey, rwndValue.toString()); + } } /** @@ -3028,7 +3108,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: { info = (NetworkInfo) msg.obj; int type = info.getType(); - updateNetworkSettings(mNetTrackers[type]); + if (mNetConfigs[type].isDefault()) updateNetworkSettings(mNetTrackers[type]); break; } } @@ -3374,6 +3454,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { String pacFileUrl = ""; if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) || !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) { + if (!proxyProperties.isValid()) { + if (DBG) + log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); + return; + } mGlobalProxy = new ProxyProperties(proxyProperties); host = mGlobalProxy.getHost(); port = mGlobalProxy.getPort(); @@ -3417,6 +3502,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { proxyProperties = new ProxyProperties(host, port, exclList); } + if (!proxyProperties.isValid()) { + if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); + return; + } + synchronized (mProxyLock) { mGlobalProxy = proxyProperties; } @@ -3441,6 +3531,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { synchronized (mProxyLock) { if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return; if (mDefaultProxy == proxy) return; // catches repeated nulls + if (proxy != null && !proxy.isValid()) { + if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString()); + return; + } mDefaultProxy = proxy; if (mGlobalProxy != null) return; @@ -3573,8 +3667,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { int user = UserHandle.getUserId(Binder.getCallingUid()); if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) { synchronized(mVpns) { - mVpns.get(user).protect(socket, - mNetTrackers[type].getLinkProperties().getInterfaceName()); + mVpns.get(user).protect(socket); } return true; } @@ -3814,7 +3907,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { boolean forwardDns) { try { mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd); - if (forwardDns) mNetd.clearDnsInterfaceForUidRange(uidStart, uidEnd); + if (forwardDns) mNetd.clearDnsInterfaceForUidRange(interfaze, uidStart, uidEnd); } catch (RemoteException e) { } @@ -3969,6 +4062,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private static final int CMP_RESULT_CODE_PROVISIONING_NETWORK = 5; + /** + * The mobile network is provisioning + */ + private static final int CMP_RESULT_CODE_IS_PROVISIONING = 6; + + private AtomicBoolean mIsProvisioningNetwork = new AtomicBoolean(false); + private AtomicBoolean mIsStartingProvisioning = new AtomicBoolean(false); + private AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false); @Override @@ -4039,11 +4140,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { setProvNotificationVisible(true, ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(), url); + // Mark that we've got a provisioning network and + // Disable Mobile Data until user actually starts provisioning. + mIsProvisioningNetwork.set(true); + MobileDataStateTracker mdst = (MobileDataStateTracker) + mNetTrackers[ConnectivityManager.TYPE_MOBILE]; + mdst.setInternalDataEnable(false); } else { if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url"); } break; } + case CMP_RESULT_CODE_IS_PROVISIONING: { + // FIXME: Need to know when provisioning is done. Probably we can + // check the completion status if successful we're done if we + // "timedout" or still connected to provisioning APN turn off data? + if (DBG) log("CheckMp.onComplete: provisioning started"); + mIsStartingProvisioning.set(false); + break; + } default: { loge("CheckMp.onComplete: ignore unexpected result=" + result); break; @@ -4066,8 +4181,28 @@ public class ConnectivityService extends IConnectivityManager.Stub { static class CheckMp extends AsyncTask<CheckMp.Params, Void, Integer> { private static final String CHECKMP_TAG = "CheckMp"; + + // adb shell setprop persist.checkmp.testfailures 1 to enable testing failures + private static boolean mTestingFailures; + + // Choosing 4 loops as half of them will use HTTPS and the other half HTTP + private static final int MAX_LOOPS = 4; + + // Number of milli-seconds to complete all of the retires public static final int MAX_TIMEOUT_MS = 60000; + + // The socket should retry only 5 seconds, the default is longer private static final int SOCKET_TIMEOUT_MS = 5000; + + // Sleep time for network errors + private static final int NET_ERROR_SLEEP_SEC = 3; + + // Sleep time for network route establishment + private static final int NET_ROUTE_ESTABLISHMENT_SLEEP_SEC = 3; + + // Short sleep time for polling :( + private static final int POLLING_SLEEP_SEC = 1; + private Context mContext; private ConnectivityService mCs; private TelephonyManager mTm; @@ -4093,6 +4228,31 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + // As explained to me by Brian Carlstrom and Kenny Root, Certificates can be + // issued by name or ip address, for Google its by name so when we construct + // this HostnameVerifier we'll pass the original Uri and use it to verify + // the host. If the host name in the original uril fails we'll test the + // hostname parameter just incase things change. + static class CheckMpHostnameVerifier implements HostnameVerifier { + Uri mOrgUri; + + CheckMpHostnameVerifier(Uri orgUri) { + mOrgUri = orgUri; + } + + @Override + public boolean verify(String hostname, SSLSession session) { + HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); + String orgUriHost = mOrgUri.getHost(); + boolean retVal = hv.verify(orgUriHost, session) || hv.verify(hostname, session); + if (DBG) { + log("isMobileOk: hostnameVerify retVal=" + retVal + " hostname=" + hostname + + " orgUriHost=" + orgUriHost); + } + return retVal; + } + } + /** * The call back object passed in Params. onComplete will be called * on the main thread. @@ -4103,6 +4263,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { } public CheckMp(Context context, ConnectivityService cs) { + if (Build.IS_DEBUGGABLE) { + mTestingFailures = + SystemProperties.getInt("persist.checkmp.testfailures", 0) == 1; + } else { + mTestingFailures = false; + } + mContext = context; mCs = cs; @@ -4141,6 +4308,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { return result; } + if (mCs.mIsStartingProvisioning.get()) { + result = CMP_RESULT_CODE_IS_PROVISIONING; + log("isMobileOk: X is provisioning result=" + result); + return result; + } + // See if we've already determined we've got a provisioning connection, // if so we don't need to do anything active. MobileDataStateTracker mdstDefault = (MobileDataStateTracker) @@ -4174,7 +4347,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mCs.setEnableFailFastMobileData(DctConstants.ENABLED); break; } - sleep(1); + sleep(POLLING_SLEEP_SEC); } } @@ -4192,7 +4365,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (VDBG) log("isMobileOk: hipri not started yet"); result = CMP_RESULT_CODE_NO_CONNECTION; - sleep(1); + sleep(POLLING_SLEEP_SEC); } // Continue trying to connect until time has run out @@ -4208,7 +4381,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("isMobileOk: not connected ni=" + mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI)); } - sleep(1); + sleep(POLLING_SLEEP_SEC); result = CMP_RESULT_CODE_NO_CONNECTION; continue; } @@ -4226,7 +4399,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Get of the addresses associated with the url host. We need to use the // address otherwise HttpURLConnection object will use the name to get - // the addresses and is will try every address but that will bypass the + // the addresses and will try every address but that will bypass the // route to host we setup and the connection could succeed as the default // interface might be connected to the internet via wifi or other interface. InetAddress[] addresses; @@ -4263,14 +4436,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { int addrTried = 0; while (true) { - // Loop through at most 3 valid addresses or until + // Loop through at most MAX_LOOPS valid addresses or until // we run out of time - if (addrTried++ >= 3) { - log("too many loops tried - giving up"); + if (addrTried++ >= MAX_LOOPS) { + log("isMobileOk: too many loops tried - giving up"); break; } if (SystemClock.elapsedRealtime() >= endTime) { - log("spend too much time - giving up"); + log("isMobileOk: spend too much time - giving up"); break; } @@ -4279,29 +4452,41 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Make a route to host so we check the specific interface. if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI, - hostAddr.getAddress())) { + hostAddr.getAddress(), null)) { // Wait a short time to be sure the route is established ?? log("isMobileOk:" + " wait to establish route to hostAddr=" + hostAddr); - sleep(3); + sleep(NET_ROUTE_ESTABLISHMENT_SLEEP_SEC); } else { log("isMobileOk:" + " could not establish route to hostAddr=" + hostAddr); + // Wait a short time before the next attempt + sleep(NET_ERROR_SLEEP_SEC); continue; } - // Rewrite the url to have numeric address to use the specific route. - // Add a pointless random query param to fool proxies into not caching. - URL newUrl = new URL(orgUri.getScheme(), - hostAddr.getHostAddress(), - orgUri.getPath() + "?q=" + rand.nextInt(Integer.MAX_VALUE)); + // Rewrite the url to have numeric address to use the specific route + // using http for half the attempts and https for the other half. + // Doing https first and http second as on a redirected walled garden + // such as t-mobile uses we get a SocketTimeoutException: "SSL + // handshake timed out" which we declare as + // CMP_RESULT_CODE_NO_TCP_CONNECTION. We could change this, but by + // having http second we will be using logic used for some time. + URL newUrl; + String scheme = (addrTried <= (MAX_LOOPS/2)) ? "https" : "http"; + newUrl = new URL(scheme, hostAddr.getHostAddress(), + orgUri.getPath()); log("isMobileOk: newUrl=" + newUrl); HttpURLConnection urlConn = null; try { - // Open the connection set the request header and get the response - urlConn = (HttpURLConnection) newUrl.openConnection( + // Open the connection set the request headers and get the response + urlConn = (HttpURLConnection)newUrl.openConnection( java.net.Proxy.NO_PROXY); + if (scheme.equals("https")) { + ((HttpsURLConnection)urlConn).setHostnameVerifier( + new CheckMpHostnameVerifier(orgUri)); + } urlConn.setInstanceFollowRedirects(false); urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConn.setReadTimeout(SOCKET_TIMEOUT_MS); @@ -4320,10 +4505,17 @@ public class ConnectivityService extends IConnectivityManager.Stub { urlConn.disconnect(); urlConn = null; + if (mTestingFailures) { + // Pretend no connection, this tests using http and https + result = CMP_RESULT_CODE_NO_CONNECTION; + log("isMobileOk: TESTING_FAILURES, pretend no connction"); + continue; + } + if (responseCode == 204) { // Return result = CMP_RESULT_CODE_CONNECTABLE; - log("isMobileOk: X expected responseCode=" + responseCode + log("isMobileOk: X got expected responseCode=" + responseCode + " result=" + result); return result; } else { @@ -4337,12 +4529,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { result = CMP_RESULT_CODE_REDIRECTED; } } catch (Exception e) { - log("isMobileOk: HttpURLConnection Exception e=" + e); + log("isMobileOk: HttpURLConnection Exception" + e); result = CMP_RESULT_CODE_NO_TCP_CONNECTION; if (urlConn != null) { urlConn.disconnect(); urlConn = null; } + sleep(NET_ERROR_SLEEP_SEC); + continue; } } log("isMobileOk: X loops|timed out result=" + result); @@ -4370,7 +4564,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("isMobileOk: connected ni=" + mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI)); } - sleep(1); + sleep(POLLING_SLEEP_SEC); continue; } } @@ -4435,7 +4629,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private void log(String s) { + private static void log(String s) { Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s); } } @@ -4454,19 +4648,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { }; private void handleMobileProvisioningAction(String url) { - // Notication mark notification as not visible + // Mark notification as not visible setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null); // If provisioning network handle as a special case, // otherwise launch browser with the intent directly. - NetworkInfo ni = getProvisioningNetworkInfo(); - if ((ni != null) && ni.isConnectedToProvisioningNetwork()) { - if (DBG) log("handleMobileProvisioningAction: on provisioning network"); + if (mIsProvisioningNetwork.get()) { + if (DBG) log("handleMobileProvisioningAction: on prov network enable then launch"); + mIsStartingProvisioning.set(true); MobileDataStateTracker mdst = (MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE]; + mdst.setEnableFailFastMobileData(DctConstants.ENABLED); mdst.enableMobileProvisioning(url); } else { - if (DBG) log("handleMobileProvisioningAction: on default network"); + if (DBG) log("handleMobileProvisioningAction: not prov network, launch browser directly"); Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER); newIntent.setData(Uri.parse(url)); diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index 8cc80f7d70d3..2bb99d68ed77 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -56,6 +56,7 @@ import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; +import android.net.ProxyProperties; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -522,6 +523,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || pm.getReceiverInfo(aa.info.getComponent(), 0, userHandle) == null) { removed = true; policy.mAdminList.remove(i); + policy.mAdminMap.remove(aa.info.getComponent()); } } catch (RemoteException re) { // Shouldn't happen @@ -2463,6 +2465,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } exclusionList = exclusionList.trim(); ContentResolver res = mContext.getContentResolver(); + + ProxyProperties proxyProperties = new ProxyProperties(data[0], proxyPort, exclusionList); + if (!proxyProperties.isValid()) { + Slog.e(TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString()); + return; + } Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]); Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort); Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags index 8eaa91d65d06..399e7d183658 100644 --- a/services/java/com/android/server/EventLogTags.logtags +++ b/services/java/com/android/server/EventLogTags.logtags @@ -123,6 +123,18 @@ option java_package com.android.server # --------------------------- # Out of memory for surfaces. 31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3) +# Task created. +31001 wm_task_created (TaskId|1|5),(StackId|1|5) +# Task moved to top (1) or bottom (0). +31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1) +# Task removed with source explanation. +31003 wm_task_removed (TaskId|1|5),(Reason|3) +# Stack created. +31004 wm_stack_created (StackId|1|5),(RelativeBoxId|1|5),(Position|1),(Weight|1|6) +# Home stack moved to top (1) or bottom (0). +31005 wm_home_stack_moved (ToTop|1) +# Stack removed. +31006 wm_stack_removed (StackId|1|5) # --------------------------- diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 562a50fafd1b..a996dbda5cf5 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -309,6 +309,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mShortcutInputMethodsAndSubtypes = new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); + // Was the keyguard locked when this client became current? + private boolean mCurClientInKeyguard; + /** * Set to true if our ServiceConnection is currently actively bound to * a service (whether or not we have gotten its IBinder back yet). @@ -385,7 +388,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private Locale mLastSystemLocale; private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor(); private final IPackageManager mIPackageManager; - private boolean mInputBoundToKeyguard; class SettingsObserver extends ContentObserver { String mLastEnabled = ""; @@ -874,12 +876,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final boolean hardKeyShown = haveHardKeyboard && conf.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES; - final boolean isScreenLocked = - mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); - final boolean isScreenSecurelyLocked = - isScreenLocked && mKeyguardManager.isKeyguardSecure(); - final boolean inputShown = mInputShown && (!isScreenLocked || mInputBoundToKeyguard); - final boolean inputActive = !isScreenSecurelyLocked && (inputShown || hardKeyShown); + + final boolean isScreenLocked = isKeyguardLocked(); + final boolean inputActive = !isScreenLocked && (mInputShown || hardKeyShown); // We assume the softkeyboard is shown when the input is active as long as the // hard keyboard is not shown. final boolean inputVisible = inputActive && !hardKeyShown; @@ -1135,19 +1134,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mNoBinding; } - if (mCurClient == null) { - mInputBoundToKeyguard = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); - if (DEBUG) { - Slog.v(TAG, "New bind. keyguard = " + mInputBoundToKeyguard); - } - } - if (mCurClient != cs) { + // Was the keyguard locked when switching over to the new client? + mCurClientInKeyguard = isKeyguardLocked(); // If the client is changing, we need to switch over to the new // one. unbindCurrentClientLocked(); if (DEBUG) Slog.v(TAG, "switching to client: client = " - + cs.client.asBinder()); + + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard); // If the screen is on, inform the new client it is active if (mScreenOn) { @@ -1499,6 +1493,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private boolean isKeyguardLocked() { + return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); + } + // Caution! This method is called in this class. Handle multi-user carefully @SuppressWarnings("deprecation") @Override @@ -1510,8 +1508,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token); return; } - synchronized (mMethodMap) { + // apply policy for binder calls + if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) { + vis = 0; + } mImeWindowVis = vis; mBackDisposition = backDisposition; if (mStatusBar != null) { diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 8f480ddbf514..eebd1c5e9642 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -1148,6 +1148,11 @@ public class LocationManagerService extends ILocationManager.Stub { boolean shouldBeEnabled = isAllowedByCurrentUserSettingsLocked(name); if (isEnabled && !shouldBeEnabled) { updateProviderListenersLocked(name, false, mCurrentUserId); + // If any provider has been disabled, clear all last locations for all providers. + // This is to be on the safe side in case a provider has location derived from + // this disabled provider. + mLastLocation.clear(); + mLastLocationCoarseInterval.clear(); changesMade = true; } else if (!isEnabled && shouldBeEnabled) { updateProviderListenersLocked(name, true, mCurrentUserId); diff --git a/services/java/com/android/server/LockSettingsService.java b/services/java/com/android/server/LockSettingsService.java index cd746cf44823..35e7afad301c 100644 --- a/services/java/com/android/server/LockSettingsService.java +++ b/services/java/com/android/server/LockSettingsService.java @@ -154,11 +154,11 @@ public class LockSettingsService extends ILockSettings.Stub { } private final void checkWritePermission(int userId) { - mContext.checkCallingOrSelfPermission(PERMISSION); + mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite"); } private final void checkPasswordReadPermission(int userId) { - mContext.checkCallingOrSelfPermission(PERMISSION); + mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead"); } private final void checkReadPermission(String requestedKey, int userId) { diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 92f99c2e4aec..72fce62e62b1 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -1567,10 +1567,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void clearDnsInterfaceForUidRange(int uid_start, int uid_end) { + public void clearDnsInterfaceForUidRange(String iface, int uid_start, int uid_end) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.execute("resolver", "clearifaceforuidrange", uid_start, uid_end); + mConnector.execute("resolver", "clearifaceforuidrange", iface, uid_start, uid_end); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 04386759df50..dedc9bd48c7e 100644 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -260,10 +260,11 @@ public class NotificationManagerService extends INotificationManager.Stub @Override public void binderDied() { - if (connection == null) { - // This is not a service; it won't be recreated. We can give up this connection. - unregisterListener(this.listener, this.userid); - } + // Remove the listener, but don't unbind from the service. The system will bring the + // service back up, and the onServiceConnected handler will readd the listener with the + // new binding. If this isn't a bound service, and is just a registered + // INotificationListener, just removing it from the list is all we need to do anyway. + removeListenerImpl(this.listener, this.userid); } /** convenience method for looking in mEnabledListenersForCurrentUser */ @@ -757,26 +758,36 @@ public class NotificationManagerService extends INotificationManager.Stub } /** - * Remove a listener binder directly + * Removes a listener from the list and unbinds from its service. */ - @Override - public void unregisterListener(INotificationListener listener, int userid) { - // no need to check permissions; if your listener binder is in the list, - // that's proof that you had permission to add it in the first place + public void unregisterListener(final INotificationListener listener, final int userid) { + if (listener == null) return; + + NotificationListenerInfo info = removeListenerImpl(listener, userid); + if (info != null && info.connection != null) { + mContext.unbindService(info.connection); + } + } + /** + * Removes a listener from the list but does not unbind from the listener's service. + * + * @return the removed listener. + */ + NotificationListenerInfo removeListenerImpl( + final INotificationListener listener, final int userid) { + NotificationListenerInfo listenerInfo = null; synchronized (mNotificationList) { final int N = mListeners.size(); for (int i=N-1; i>=0; i--) { final NotificationListenerInfo info = mListeners.get(i); if (info.listener.asBinder() == listener.asBinder() && info.userid == userid) { - mListeners.remove(i); - if (info.connection != null) { - mContext.unbindService(info.connection); - } + listenerInfo = mListeners.remove(i); } } } + return listenerInfo; } /** diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java index e0f415bfeaa4..16d2468e7c9d 100644 --- a/services/java/com/android/server/NsdService.java +++ b/services/java/com/android/server/NsdService.java @@ -483,10 +483,14 @@ public class NsdService extends INsdManager.Stub { clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4])); stopResolveService(id); - if (!getAddrInfo(id, cooked[3])) { + removeRequestMap(clientId, id, clientInfo); + + int id2 = getUniqueId(); + if (getAddrInfo(id2, cooked[3])) { + storeRequestMap(clientId, id2, clientInfo); + } else { clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, NsdManager.FAILURE_INTERNAL_ERROR, clientId); - removeRequestMap(clientId, id, clientInfo); clientInfo.mResolvedService = null; } break; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0e0f15625823..a42cbcfe0d64 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -55,6 +55,7 @@ import com.android.server.content.ContentService; import com.android.server.display.DisplayManagerService; import com.android.server.dreams.DreamManagerService; import com.android.server.input.InputManagerService; +import com.android.server.media.MediaRouterService; import com.android.server.net.NetworkPolicyManagerService; import com.android.server.net.NetworkStatsService; import com.android.server.os.SchedulingPolicyService; @@ -356,6 +357,7 @@ class ServerThread { DreamManagerService dreamy = null; AssetAtlasService atlas = null; PrintManagerService printManager = null; + MediaRouterService mediaRouter = null; // Bring up services needed for UI. if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { @@ -804,6 +806,16 @@ class ServerThread { } catch (Throwable e) { reportWtf("starting Print Service", e); } + + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "Media Router Service"); + mediaRouter = new MediaRouterService(context); + ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter); + } catch (Throwable e) { + reportWtf("starting MediaRouterService", e); + } + } } // Before things start rolling, be sure we have decided whether @@ -916,6 +928,7 @@ class ServerThread { final InputManagerService inputManagerF = inputManager; final TelephonyRegistry telephonyRegistryF = telephonyRegistry; final PrintManagerService printManagerF = printManager; + final MediaRouterService mediaRouterF = mediaRouter; // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state @@ -1063,6 +1076,12 @@ class ServerThread { } catch (Throwable e) { reportWtf("Notifying PrintManagerService running", e); } + + try { + if (mediaRouterF != null) mediaRouterF.systemRunning(); + } catch (Throwable e) { + reportWtf("Notifying MediaRouterService running", e); + } } }); @@ -1104,6 +1123,19 @@ public class SystemServer { private static native void nativeInit(); public static void main(String[] args) { + + /* + * In case the runtime switched since last boot (such as when + * the old runtime was removed in an OTA), set the system + * property so that it is in sync. We can't do this in + * libnativehelper's JniInvocation::Init code where we already + * had to fallback to a different runtime because it is + * running as root and we need to be the system user to set + * the property. http://b/11463182 + */ + SystemProperties.set("persist.sys.dalvik.vm.lib", + VMRuntime.getRuntime().vmLibrary()); + if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) { // If a device's clock is before 1970 (before 0), a lot of // APIs crash dealing with negative numbers, notably diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index 6957bac0c1eb..e6b6b93a1d46 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -40,6 +40,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.graphics.Point; import android.os.Binder; import android.os.Bundle; import android.os.Environment; @@ -637,6 +638,14 @@ class WallpaperManagerService extends IWallpaperManager.Stub { return false; } + private Point getDefaultDisplaySize() { + Point p = new Point(); + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + Display d = wm.getDefaultDisplay(); + d.getRealSize(p); + return p; + } + public void setDimensionHints(int width, int height) throws RemoteException { checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); synchronized (mLock) { @@ -648,6 +657,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("width and height must be > 0"); } + // Make sure it is at least as large as the display. + Point displaySize = getDefaultDisplaySize(); + width = Math.max(width, displaySize.x); + height = Math.max(height, displaySize.y); if (width != wallpaper.width || height != wallpaper.height) { wallpaper.width = width; @@ -1146,9 +1159,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } // We always want to have some reasonable width hint. - WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); - Display d = wm.getDefaultDisplay(); - int baseSize = d.getMaximumSizeDimension(); + int baseSize = getMaximumSizeDimension(); if (wallpaper.width < baseSize) { wallpaper.width = baseSize; } @@ -1157,6 +1168,12 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } + private int getMaximumSizeDimension() { + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + Display d = wm.getDefaultDisplay(); + return d.getMaximumSizeDimension(); + } + // Called by SystemBackupAgent after files are restored to disk. void settingsRestored() { // TODO: If necessary, make it work for secondary users as well. This currently assumes diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index a99b58a256e6..43f12ebedde1 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -76,7 +76,7 @@ class TouchExplorer implements EventStreamTransformation { private static final int STATE_DELEGATING = 0x00000004; private static final int STATE_GESTURE_DETECTING = 0x00000005; - // The minimum of the cosine between the vectors of two moving + // The maximum of the cosine between the vectors of two moving // pointers so they can be considered moving in the same direction. private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4) @@ -436,13 +436,19 @@ class TouchExplorer implements EventStreamTransformation { final int pointerIdBits = (1 << pointerId); mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, policyFlags); + } else { + // Cache the event until we discern exploration from gesturing. + mSendHoverEnterAndMoveDelayed.addEvent(event); } - // Cache the event until we discern exploration from gesturing. - mSendHoverEnterAndMoveDelayed.addEvent(event); } } break; case MotionEvent.ACTION_POINTER_DOWN: { - /* do nothing - let the code for ACTION_MOVE decide what to do */ + // Another finger down means that if we have not started to deliver + // hover events, we will not have to. The code for ACTION_MOVE will + // decide what we will actually do next. + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mPerformLongPressDelayed.cancel(); } break; case MotionEvent.ACTION_MOVE: { final int pointerId = receivedTracker.getPrimaryPointerId(); @@ -518,14 +524,11 @@ class TouchExplorer implements EventStreamTransformation { mPerformLongPressDelayed.cancel(); } } - // The user is either double tapping or performing a long - // press, so do not send move events yet. - if (mDoubleTapDetector.firstTapDetected()) { - break; + if (mTouchExplorationInProgress) { + sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); + sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, + policyFlags); } - sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, - policyFlags); } } break; case 2: { @@ -539,22 +542,24 @@ class TouchExplorer implements EventStreamTransformation { mPerformLongPressDelayed.cancel(); } else { mPerformLongPressDelayed.cancel(); - // If the user is touch exploring the second pointer may be - // performing a double tap to activate an item without need - // for the user to lift his exploring finger. - // It is *important* to use the distance traveled by the pointers - // on the screen which may or may not be magnified. - final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) - - rawEvent.getX(pointerIndex); - final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId) - - rawEvent.getY(pointerIndex); - final double moveDelta = Math.hypot(deltaX, deltaY); - if (moveDelta < mDoubleTapSlop) { - break; + if (mTouchExplorationInProgress) { + // If the user is touch exploring the second pointer may be + // performing a double tap to activate an item without need + // for the user to lift his exploring finger. + // It is *important* to use the distance traveled by the pointers + // on the screen which may or may not be magnified. + final float deltaX = receivedTracker.getReceivedPointerDownX( + pointerId) - rawEvent.getX(pointerIndex); + final float deltaY = receivedTracker.getReceivedPointerDownY( + pointerId) - rawEvent.getY(pointerIndex); + final double moveDelta = Math.hypot(deltaX, deltaY); + if (moveDelta < mDoubleTapSlop) { + break; + } + // We are sending events so send exit and gesture + // end since we transition to another state. + sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } - // We are sending events so send exit and gesture - // end since we transition to another state. - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } // We know that a new state transition is to happen and the @@ -736,20 +741,34 @@ class TouchExplorer implements EventStreamTransformation { + "there is at least one pointer down!"); } case MotionEvent.ACTION_UP: { - mAms.onTouchInteractionEnd(); + // Offset the event if we are doing a long press as the + // target is not necessarily under the user's finger. + if (mLongPressingPointerId >= 0) { + event = offsetEvent(event, - mLongPressingPointerDeltaX, + - mLongPressingPointerDeltaY); + // Clear the long press state. + mLongPressingPointerId = -1; + mLongPressingPointerDeltaX = 0; + mLongPressingPointerDeltaY = 0; + } + + // Deliver the event. + sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); + // Announce the end of a the touch interaction. + mAms.onTouchInteractionEnd(); sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); - mLongPressingPointerId = -1; - mLongPressingPointerDeltaX = 0; - mLongPressingPointerDeltaY = 0; + mCurrentState = STATE_TOUCH_EXPLORING; } break; case MotionEvent.ACTION_CANCEL: { clear(event, policyFlags); } break; + default: { + // Deliver the event. + sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); + } } - // Deliver the event. - sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); } private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) { @@ -959,27 +978,8 @@ class TouchExplorer implements EventStreamTransformation { // long press it, or even worse to avoid the user long pressing // on the wrong item since click and long press behave differently. if (mLongPressingPointerId >= 0) { - final int remappedIndex = event.findPointerIndex(mLongPressingPointerId); - final int pointerCount = event.getPointerCount(); - PointerProperties[] props = PointerProperties.createArray(pointerCount); - PointerCoords[] coords = PointerCoords.createArray(pointerCount); - for (int i = 0; i < pointerCount; i++) { - event.getPointerProperties(i, props[i]); - event.getPointerCoords(i, coords[i]); - if (i == remappedIndex) { - coords[i].x -= mLongPressingPointerDeltaX; - coords[i].y -= mLongPressingPointerDeltaY; - } - } - MotionEvent remapped = MotionEvent.obtain(event.getDownTime(), - event.getEventTime(), event.getAction(), event.getPointerCount(), - props, coords, event.getMetaState(), event.getButtonState(), - 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), - event.getSource(), event.getFlags()); - if (event != prototype) { - event.recycle(); - } - event = remapped; + event = offsetEvent(event, - mLongPressingPointerDeltaX, + - mLongPressingPointerDeltaY); } if (DEBUG) { @@ -1004,6 +1004,39 @@ class TouchExplorer implements EventStreamTransformation { } /** + * Offsets all pointers in the given event by adding the specified X and Y + * offsets. + * + * @param event The event to offset. + * @param offsetX The X offset. + * @param offsetY The Y offset. + * @return An event with the offset pointers or the original event if both + * offsets are zero. + */ + private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) { + if (offsetX == 0 && offsetY == 0) { + return event; + } + final int remappedIndex = event.findPointerIndex(mLongPressingPointerId); + final int pointerCount = event.getPointerCount(); + PointerProperties[] props = PointerProperties.createArray(pointerCount); + PointerCoords[] coords = PointerCoords.createArray(pointerCount); + for (int i = 0; i < pointerCount; i++) { + event.getPointerProperties(i, props[i]); + event.getPointerCoords(i, coords[i]); + if (i == remappedIndex) { + coords[i].x += offsetX; + coords[i].y += offsetY; + } + } + return MotionEvent.obtain(event.getDownTime(), + event.getEventTime(), event.getAction(), event.getPointerCount(), + props, coords, event.getMetaState(), event.getButtonState(), + 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), + event.getSource(), event.getFlags()); + } + + /** * Computes the action for an injected event based on a masked action * and a pointer index. * @@ -1674,7 +1707,6 @@ class TouchExplorer implements EventStreamTransformation { // Keep track of the last up pointer data. private long mLastReceivedUpPointerDownTime; - private int mLastReceivedUpPointerId; private float mLastReceivedUpPointerDownX; private float mLastReceivedUpPointerDownY; @@ -1690,7 +1722,6 @@ class TouchExplorer implements EventStreamTransformation { mReceivedPointersDown = 0; mPrimaryPointerId = 0; mLastReceivedUpPointerDownTime = 0; - mLastReceivedUpPointerId = 0; mLastReceivedUpPointerDownX = 0; mLastReceivedUpPointerDownY = 0; } @@ -1823,7 +1854,6 @@ class TouchExplorer implements EventStreamTransformation { final int pointerId = event.getPointerId(pointerIndex); final int pointerFlag = (1 << pointerId); - mLastReceivedUpPointerId = 0; mLastReceivedUpPointerDownTime = 0; mLastReceivedUpPointerDownX = 0; mLastReceivedUpPointerDownX = 0; @@ -1848,7 +1878,6 @@ class TouchExplorer implements EventStreamTransformation { final int pointerId = event.getPointerId(pointerIndex); final int pointerFlag = (1 << pointerId); - mLastReceivedUpPointerId = pointerId; mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId]; mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId]; diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java index f972f70f41e2..aa9849ef5641 100644 --- a/services/java/com/android/server/accounts/AccountManagerService.java +++ b/services/java/com/android/server/accounts/AccountManagerService.java @@ -190,10 +190,10 @@ public class AccountManagerService private final HashMap<String, Account[]> accountCache = new LinkedHashMap<String, Account[]>(); /** protected by the {@link #cacheLock} */ - private HashMap<Account, HashMap<String, String>> userDataCache = + private final HashMap<Account, HashMap<String, String>> userDataCache = new HashMap<Account, HashMap<String, String>>(); /** protected by the {@link #cacheLock} */ - private HashMap<Account, HashMap<String, String>> authTokenCache = + private final HashMap<Account, HashMap<String, String>> authTokenCache = new HashMap<Account, HashMap<String, String>>(); UserAccounts(Context context, int userId) { @@ -475,6 +475,7 @@ public class AccountManagerService validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */); } + @Override public String getPassword(Account account) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getPassword: " + account @@ -514,6 +515,7 @@ public class AccountManagerService } } + @Override public String getUserData(Account account, String key) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getUserData: " + account @@ -533,6 +535,7 @@ public class AccountManagerService } } + @Override public AuthenticatorDescription[] getAuthenticatorTypes() { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getAuthenticatorTypes: " @@ -763,6 +766,7 @@ public class AccountManagerService return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values); } + @Override public void hasFeatures(IAccountManagerResponse response, Account account, String[] features) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -840,6 +844,7 @@ public class AccountManagerService } } + @Override public void removeAccount(IAccountManagerResponse response, Account account) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "removeAccount: " + account @@ -1049,6 +1054,7 @@ public class AccountManagerService } } + @Override public String peekAuthToken(Account account, String authTokenType) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "peekAuthToken: " + account @@ -1068,6 +1074,7 @@ public class AccountManagerService } } + @Override public void setAuthToken(Account account, String authTokenType, String authToken) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "setAuthToken: " + account @@ -1087,6 +1094,7 @@ public class AccountManagerService } } + @Override public void setPassword(Account account, String password) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "setAuthToken: " + account @@ -1135,6 +1143,7 @@ public class AccountManagerService mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId)); } + @Override public void clearPassword(Account account) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "clearPassword: " + account @@ -1152,6 +1161,7 @@ public class AccountManagerService } } + @Override public void setUserData(Account account, String key, String value) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "setUserData: " + account @@ -1225,6 +1235,7 @@ public class AccountManagerService } } + @Override public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType, final String authTokenType) throws RemoteException { @@ -1271,6 +1282,7 @@ public class AccountManagerService } } + @Override public void getAuthToken(IAccountManagerResponse response, final Account account, final String authTokenType, final boolean notifyOnAuthFailure, final boolean expectActivityLaunch, Bundle loginOptionsIn) { @@ -1284,8 +1296,22 @@ public class AccountManagerService + ", pid " + Binder.getCallingPid()); } if (response == null) throw new IllegalArgumentException("response is null"); - if (account == null) throw new IllegalArgumentException("account is null"); - if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + try { + if (account == null) { + Slog.w(TAG, "getAuthToken called with null account"); + response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "account is null"); + return; + } + if (authTokenType == null) { + Slog.w(TAG, "getAuthToken called with null authTokenType"); + response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "authTokenType is null"); + return; + } + } catch (RemoteException e) { + Slog.w(TAG, "Failed to report error back to the client." + e); + return; + } + checkBinderPermission(Manifest.permission.USE_CREDENTIALS); final UserAccounts accounts = getUserAccountsForCaller(); final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo; @@ -1294,11 +1320,6 @@ public class AccountManagerService final boolean customTokens = authenticatorInfo != null && authenticatorInfo.type.customTokens; - // Check to see that the app is authorized to access the account, in case it's a - // restricted account. - if (!ArrayUtils.contains(getAccounts((String) null), account)) { - throw new IllegalArgumentException("no such account"); - } // skip the check if customTokens final int callerUid = Binder.getCallingUid(); final boolean permissionGranted = customTokens || @@ -1472,6 +1493,7 @@ public class AccountManagerService return id; } + @Override public void addAccount(final IAccountManagerResponse response, final String accountType, final String authTokenType, final String[] requiredFeatures, final boolean expectActivityLaunch, final Bundle optionsIn) { @@ -1582,6 +1604,7 @@ public class AccountManagerService } } + @Override public void updateCredentials(IAccountManagerResponse response, final Account account, final String authTokenType, final boolean expectActivityLaunch, final Bundle loginOptions) { @@ -1620,6 +1643,7 @@ public class AccountManagerService } } + @Override public void editProperties(IAccountManagerResponse response, final String accountType, final boolean expectActivityLaunch) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -1657,7 +1681,7 @@ public class AccountManagerService private volatile Account[] mAccountsOfType = null; private volatile ArrayList<Account> mAccountsWithFeatures = null; private volatile int mCurrentAccount = 0; - private int mCallingUid; + private final int mCallingUid; public GetAccountsByTypeAndFeatureSession(UserAccounts accounts, IAccountManagerResponse response, String type, String[] features, int callingUid) { @@ -1941,6 +1965,7 @@ public class AccountManagerService return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, packageUid); } + @Override public void getAccountsByFeatures(IAccountManagerResponse response, String type, String[] features) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -2069,6 +2094,7 @@ public class AccountManagerService unbind(); } + @Override public void binderDied() { mResponse = null; close(); @@ -2112,6 +2138,7 @@ public class AccountManagerService mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this); } + @Override public void onServiceConnected(ComponentName name, IBinder service) { mAuthenticator = IAccountAuthenticator.Stub.asInterface(service); try { @@ -2122,6 +2149,7 @@ public class AccountManagerService } } + @Override public void onServiceDisconnected(ComponentName name) { mAuthenticator = null; IAccountManagerResponse response = getResponseAndClose(); @@ -2217,7 +2245,14 @@ public class AccountManagerService Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + response); } - response.onResult(result); + if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) && + (intent == null)) { + // All AccountManager error codes are greater than 0 + response.onError(result.getInt(AccountManager.KEY_ERROR_CODE), + result.getString(AccountManager.KEY_ERROR_MESSAGE)); + } else { + response.onResult(result); + } } } catch (RemoteException e) { // if the caller is dead then there is no one to care about remote exceptions @@ -2228,10 +2263,12 @@ public class AccountManagerService } } + @Override public void onRequestContinued() { mNumRequestContinued++; } + @Override public void onError(int errorCode, String errorMessage) { mNumErrors++; IAccountManagerResponse response = getResponseAndClose(); @@ -2731,6 +2768,7 @@ public class AccountManagerService return true; } + @Override public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) throws RemoteException { final int callingUid = getCallingUid(); diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java index a64940ca2420..17bb3e0f525b 100644 --- a/services/java/com/android/server/am/ActiveServices.java +++ b/services/java/com/android/server/am/ActiveServices.java @@ -26,6 +26,7 @@ import java.util.List; import android.os.Handler; import android.os.Looper; +import android.os.SystemProperties; import android.util.ArrayMap; import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; @@ -63,7 +64,7 @@ public final class ActiveServices { static final boolean DEBUG_SERVICE = ActivityManagerService.DEBUG_SERVICE; static final boolean DEBUG_SERVICE_EXECUTING = ActivityManagerService.DEBUG_SERVICE_EXECUTING; static final boolean DEBUG_DELAYED_SERVICE = ActivityManagerService.DEBUG_SERVICE; - static final boolean DEBUG_DELAYED_STATS = DEBUG_DELAYED_SERVICE; + static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE; static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU; static final String TAG = ActivityManagerService.TAG; static final String TAG_MU = ActivityManagerService.TAG_MU; @@ -76,7 +77,7 @@ public final class ActiveServices { // How long a service needs to be running until restarting its process // is no longer considered to be a relaunch of the service. - static final int SERVICE_RESTART_DURATION = 5*1000; + static final int SERVICE_RESTART_DURATION = 1*1000; // How long a service needs to be running until it will start back at // SERVICE_RESTART_DURATION after being killed. @@ -185,11 +186,11 @@ public final class ActiveServices { void ensureNotStartingBackground(ServiceRecord r) { if (mStartingBackground.remove(r)) { - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer background starting: " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer background starting: " + r); rescheduleDelayedStarts(); } if (mDelayedStartList.remove(r)) { - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer delaying start: " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer delaying start: " + r); } } @@ -207,7 +208,7 @@ public final class ActiveServices { while (mDelayedStartList.size() > 0 && mStartingBackground.size() < mMaxStartingBackground) { ServiceRecord r = mDelayedStartList.remove(0); - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r); if (r.pendingStarts.size() <= 0) { Slog.w(TAG, "**** NO PENDING STARTS! " + r + " startReq=" + r.startRequested + " delayedStop=" + r.delayedStop); @@ -239,7 +240,12 @@ public final class ActiveServices { public ActiveServices(ActivityManagerService service) { mAm = service; - mMaxStartingBackground = ActivityManager.isLowRamDeviceStatic() ? 1 : 3; + int maxBg = 0; + try { + maxBg = Integer.parseInt(SystemProperties.get("ro.config.max_starting_bg", "0")); + } catch(RuntimeException e) { + } + mMaxStartingBackground = maxBg > 0 ? maxBg : ActivityManager.isLowRamDeviceStatic() ? 1 : 3; } ServiceRecord getServiceByName(ComponentName name, int callingUser) { @@ -270,7 +276,7 @@ public final class ActiveServices { ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, int userId) { - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "startService: " + service + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "startService: " + service + " type=" + resolvedType + " args=" + service.getExtras()); final boolean callerFg; @@ -301,7 +307,7 @@ public final class ActiveServices { ServiceRecord r = res.record; NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked( callingUid, r.packageName, service, service.getFlags(), null); - if (unscheduleServiceRestartLocked(r)) { + if (unscheduleServiceRestartLocked(r, callingUid, false)) { if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r); } r.lastActivity = SystemClock.uptimeMillis(); @@ -330,7 +336,7 @@ public final class ActiveServices { if (r.delayed) { // This service is already scheduled for a delayed start; just leave // it still waiting. - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Continuing to delay: " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Continuing to delay: " + r); return r.name; } if (smap.mStartingBackground.size() >= mMaxStartingBackground) { @@ -340,15 +346,15 @@ public final class ActiveServices { r.delayed = true; return r.name; } - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying: " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Not delaying: " + r); addToStarting = true; } else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) { // We slightly loosen when we will enqueue this new service as a background // starting service we are waiting for, to also include processes that are // currently running other services or receivers. addToStarting = true; - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying, but counting as bg: " + r); - } else if (DEBUG_DELAYED_STATS) { + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Not delaying, but counting as bg: " + r); + } else if (DEBUG_DELAYED_STARTS) { StringBuilder sb = new StringBuilder(128); sb.append("Not potential delay (state=").append(proc.curProcState) .append(' ').append(proc.adjType); @@ -361,7 +367,7 @@ public final class ActiveServices { sb.append(r.toString()); Slog.v(TAG, sb.toString()); } - } else if (DEBUG_DELAYED_STATS) { + } else if (DEBUG_DELAYED_STARTS) { if (callerFg) { Slog.v(TAG, "Not potential delay (callerFg=" + callerFg + " uid=" + callingUid + " pid=" + callingPid + "): " + r); @@ -398,7 +404,7 @@ public final class ActiveServices { RuntimeException here = new RuntimeException("here"); here.fillInStackTrace(); Slog.v(TAG, "Starting background (first=" + first + "): " + r, here); - } else if (DEBUG_DELAYED_STATS) { + } else if (DEBUG_DELAYED_STARTS) { Slog.v(TAG, "Starting background (first=" + first + "): " + r); } if (first) { @@ -416,7 +422,7 @@ public final class ActiveServices { // If service isn't actually running, but is is being held in the // delayed list, then we need to keep it started but note that it // should be stopped once no longer delayed. - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Delaying stop of pending: " + service); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Delaying stop of pending: " + service); service.delayedStop = true; return; } @@ -564,7 +570,7 @@ public final class ActiveServices { if (r.isForeground) { r.isForeground = false; if (r.app != null) { - mAm.updateLruProcessLocked(r.app, false, false); + mAm.updateLruProcessLocked(r.app, false, null); updateServiceForegroundLocked(r.app, true); } } @@ -597,6 +603,42 @@ public final class ActiveServices { } } + private boolean updateServiceClientActivitiesLocked(ProcessRecord proc, + ConnectionRecord modCr) { + if (modCr != null && modCr.binding.client != null) { + if (modCr.binding.client.activities.size() <= 0) { + // This connection is from a client without activities, so adding + // and removing is not interesting. + return false; + } + } + + boolean anyClientActivities = false; + for (int i=proc.services.size()-1; i>=0 && !anyClientActivities; i--) { + ServiceRecord sr = proc.services.valueAt(i); + for (int conni=sr.connections.size()-1; conni>=0 && !anyClientActivities; conni--) { + ArrayList<ConnectionRecord> clist = sr.connections.valueAt(conni); + for (int cri=clist.size()-1; cri>=0; cri--) { + ConnectionRecord cr = clist.get(cri); + if (cr.binding.client == null || cr.binding.client == proc) { + // Binding to ourself is not interesting. + continue; + } + if (cr.binding.client.activities.size() > 0) { + anyClientActivities = true; + break; + } + } + } + } + if (anyClientActivities != proc.hasClientActivities) { + proc.hasClientActivities = anyClientActivities; + mAm.updateLruProcessLocked(proc, anyClientActivities, null); + return true; + } + return false; + } + int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, int userId) { @@ -659,7 +701,7 @@ public final class ActiveServices { final long origId = Binder.clearCallingIdentity(); try { - if (unscheduleServiceRestartLocked(s)) { + if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) { if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: " + s); } @@ -698,6 +740,9 @@ public final class ActiveServices { if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { b.client.hasAboveClient = true; } + if (s.app != null) { + updateServiceClientActivitiesLocked(s.app, c); + } clist = mServiceConnections.get(binder); if (clist == null) { clist = new ArrayList<ConnectionRecord>(); @@ -714,6 +759,7 @@ public final class ActiveServices { if (s.app != null) { // This could have made the service more important. + mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities, b.client); mAm.updateOomAdjLocked(s.app); } @@ -956,14 +1002,11 @@ public final class ActiveServices { smap.mServicesByIntent.put(filter, r); // Make sure this component isn't in the pending list. - int N = mPendingServices.size(); - for (int i=0; i<N; i++) { + for (int i=mPendingServices.size()-1; i>=0; i--) { ServiceRecord pr = mPendingServices.get(i); if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid && pr.name.equals(name)) { mPendingServices.remove(i); - i--; - N--; } } } @@ -1055,6 +1098,14 @@ public final class ActiveServices { boolean allowCancel) { boolean canceled = false; + ServiceMap smap = getServiceMap(r.userId); + if (smap.mServicesByName.get(r.name) != r) { + ServiceRecord cur = smap.mServicesByName.get(r.name); + Slog.wtf(TAG, "Attempting to schedule restart of " + r + + " when found in map: " + cur); + return false; + } + final long now = SystemClock.uptimeMillis(); if ((r.serviceInfo.applicationInfo.flags @@ -1101,16 +1152,9 @@ public final class ActiveServices { r.restartCount = 1; r.restartDelay = minDuration; } else { - if ((r.serviceInfo.applicationInfo.flags - &ApplicationInfo.FLAG_PERSISTENT) != 0) { - // Services in peristent processes will restart much more - // quickly, since they are pretty important. (Think SystemUI). - r.restartDelay += minDuration/2; - } else { - r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; - if (r.restartDelay < minDuration) { - r.restartDelay = minDuration; - } + r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; + if (r.restartDelay < minDuration) { + r.restartDelay = minDuration; } } } @@ -1137,7 +1181,7 @@ public final class ActiveServices { } while (repeat); } else { - // Persistent processes are immediately restrted, so there is no + // Persistent processes are immediately restarted, so there is no // reason to hold of on restarting their services. r.totalRestartCount++; r.restartCount = 0; @@ -1148,6 +1192,7 @@ public final class ActiveServices { if (!mRestartingServices.contains(r)) { r.createdFromFg = false; mRestartingServices.add(r); + r.makeRestarting(mAm.mProcessStats.getMemFactorLocked(), now); } r.cancelNotification(); @@ -1170,16 +1215,44 @@ public final class ActiveServices { bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true); } - private final boolean unscheduleServiceRestartLocked(ServiceRecord r) { - if (r.restartDelay == 0) { + private final boolean unscheduleServiceRestartLocked(ServiceRecord r, int callingUid, + boolean force) { + if (!force && r.restartDelay == 0) { return false; } - r.resetRestartCounter(); - mRestartingServices.remove(r); + // Remove from the restarting list; if the service is currently on the + // restarting list, or the call is coming from another app, then this + // service has become of much more interest so we reset the restart interval. + boolean removed = mRestartingServices.remove(r); + if (removed || callingUid != r.appInfo.uid) { + r.resetRestartCounter(); + } + if (removed) { + clearRestartingIfNeededLocked(r); + } mAm.mHandler.removeCallbacks(r.restarter); return true; } + private void clearRestartingIfNeededLocked(ServiceRecord r) { + if (r.restartTracker != null) { + // If this is the last restarting record with this tracker, then clear + // the tracker's restarting state. + boolean stillTracking = false; + for (int i=mRestartingServices.size()-1; i>=0; i--) { + if (mRestartingServices.get(i).restartTracker == r.restartTracker) { + stillTracking = true; + break; + } + } + if (!stillTracking) { + r.restartTracker.setRestarting(false, mAm.mProcessStats.getMemFactorLocked(), + SystemClock.uptimeMillis()); + r.restartTracker = null; + } + } + } + private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting) { //Slog.i(TAG, "Bring up service:"); @@ -1199,11 +1272,13 @@ public final class ActiveServices { // We are now bringing the service up, so no longer in the // restarting state. - mRestartingServices.remove(r); + if (mRestartingServices.remove(r)) { + clearRestartingIfNeededLocked(r); + } // Make sure this service is no longer considered delayed, we are starting it now. if (r.delayed) { - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r); getServiceMap(r.userId).mDelayedStartList.remove(r); r.delayed = false; } @@ -1286,7 +1361,7 @@ public final class ActiveServices { // Oh and hey we've already been asked to stop! r.delayedStop = false; if (r.startRequested) { - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r); stopServiceLocked(r); } } @@ -1316,7 +1391,8 @@ public final class ActiveServices { app.services.add(r); bumpServiceExecutingLocked(r, execInFg, "create"); - mAm.updateLruProcessLocked(app, true, false); + mAm.updateLruProcessLocked(app, false, null); + mAm.updateOomAdjLocked(); boolean created = false; try { @@ -1338,6 +1414,7 @@ public final class ActiveServices { } finally { if (!created) { app.services.remove(r); + r.app = null; scheduleServiceRestartLocked(r, false); } } @@ -1355,7 +1432,7 @@ public final class ActiveServices { sendServiceArgsLocked(r, execInFg, true); if (r.delayed) { - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r); getServiceMap(r.userId).mDelayedStartList.remove(r); r.delayed = false; } @@ -1364,7 +1441,7 @@ public final class ActiveServices { // Oh and hey we've already been asked to stop! r.delayedStop = false; if (r.startRequested) { - if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (from start): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (from start): " + r); stopServiceLocked(r); } } @@ -1508,16 +1585,13 @@ public final class ActiveServices { smap.mServicesByName.remove(r.name); smap.mServicesByIntent.remove(r.intent); r.totalRestartCount = 0; - unscheduleServiceRestartLocked(r); + unscheduleServiceRestartLocked(r, 0, true); // Also make sure it is not on the pending list. - int N = mPendingServices.size(); - for (int i=0; i<N; i++) { + for (int i=mPendingServices.size()-1; i>=0; i--) { if (mPendingServices.get(i) == r) { mPendingServices.remove(i); if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r); - i--; - N--; } } @@ -1536,6 +1610,7 @@ public final class ActiveServices { } r.app.services.remove(r); if (r.app.thread != null) { + updateServiceForegroundLocked(r.app, false); try { bumpServiceExecutingLocked(r, false, "destroy"); mDestroyingServices.add(r); @@ -1546,7 +1621,6 @@ public final class ActiveServices { + r.shortName, e); serviceProcessGoneLocked(r); } - updateServiceForegroundLocked(r.app, false); } else { if (DEBUG_SERVICE) Slog.v( TAG, "Removed service that has no process: " + r); @@ -1601,6 +1675,9 @@ public final class ActiveServices { if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { b.client.updateHasAboveClientLocked(); } + if (s.app != null) { + updateServiceClientActivitiesLocked(s.app, c); + } } clist = mServiceConnections.get(binder); if (clist != null) { @@ -1621,6 +1698,13 @@ public final class ActiveServices { && b.intent.hasBound) { try { bumpServiceExecutingLocked(s, false, "unbind"); + if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0 + && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) { + // If this service's process is not already in the cached list, + // then update it in the LRU list here because this may be causing + // it to go down there and we want it to start out near the top. + mAm.updateLruProcessLocked(s.app, false, null); + } mAm.updateOomAdjLocked(s.app); b.intent.hasBound = false; // Assume the client doesn't want to know about a rebind; @@ -1714,6 +1798,7 @@ public final class ActiveServices { long now = SystemClock.uptimeMillis(); r.tracker.setExecuting(false, memFactor, now); r.tracker.setBound(false, memFactor, now); + r.tracker.setStarted(false, memFactor, now); } serviceDoneExecutingLocked(r, true, true); } @@ -1761,6 +1846,12 @@ public final class ActiveServices { r.tracker = null; } } + if (finishing) { + if (r.app != null && !r.app.persistent) { + r.app.services.remove(r); + } + r.app = null; + } } } @@ -1839,6 +1930,9 @@ public final class ActiveServices { Slog.i(TAG, " Force stopping service " + service); if (service.app != null) { service.app.removed = true; + if (!service.app.persistent) { + service.app.services.remove(service); + } } service.app = null; service.isolatedProc = null; @@ -1905,8 +1999,7 @@ public final class ActiveServices { } } - final void killServicesLocked(ProcessRecord app, - boolean allowRestart) { + final void killServicesLocked(ProcessRecord app, boolean allowRestart) { // Report disconnected services. if (false) { // XXX we are letting the client link to the service for @@ -1935,20 +2028,15 @@ public final class ActiveServices { } } - // Clean up any connections this application has to other services. - for (int i=app.connections.size()-1; i>=0; i--) { - ConnectionRecord r = app.connections.valueAt(i); - removeConnectionLocked(r, app, null); - } - app.connections.clear(); - + // First clear app state from services. for (int i=app.services.size()-1; i>=0; i--) { - // Any services running in the application need to be placed - // back in the pending list. ServiceRecord sr = app.services.valueAt(i); synchronized (sr.stats.getBatteryStats()) { sr.stats.stopLaunchedLocked(); } + if (sr.app != app && sr.app != null && !sr.app.persistent) { + sr.app.services.remove(sr); + } sr.app = null; sr.isolatedProc = null; sr.executeNesting = 0; @@ -1965,8 +2053,39 @@ public final class ActiveServices { b.binder = null; b.requested = b.received = b.hasBound = false; } + } - if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags + // Clean up any connections this application has to other services. + for (int i=app.connections.size()-1; i>=0; i--) { + ConnectionRecord r = app.connections.valueAt(i); + removeConnectionLocked(r, app, null); + } + app.connections.clear(); + + ServiceMap smap = getServiceMap(app.userId); + + // Now do remaining service cleanup. + for (int i=app.services.size()-1; i>=0; i--) { + ServiceRecord sr = app.services.valueAt(i); + + // Unless the process is persistent, this process record is going away, + // so make sure the service is cleaned out of it. + if (!app.persistent) { + app.services.removeAt(i); + } + + // Sanity check: if the service listed for the app is not one + // we actually are maintaining, just let it drop. + if (smap.mServicesByName.get(sr.name) != sr) { + ServiceRecord cur = smap.mServicesByName.get(sr.name); + Slog.wtf(TAG, "Service " + sr + " in process " + app + + " not same as in map: " + cur); + continue; + } + + // Any services running in the application may need to be placed + // back in the pending list. + if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags &ApplicationInfo.FLAG_PERSISTENT) == 0) { Slog.w(TAG, "Service crashed " + sr.crashCount + " times, stopping: " + sr); @@ -1999,6 +2118,23 @@ public final class ActiveServices { if (!allowRestart) { app.services.clear(); + + // Make sure there are no more restarting services for this process. + for (int i=mRestartingServices.size()-1; i>=0; i--) { + ServiceRecord r = mRestartingServices.get(i); + if (r.processName.equals(app.processName) && + r.serviceInfo.applicationInfo.uid == app.info.uid) { + mRestartingServices.remove(i); + clearRestartingIfNeededLocked(r); + } + } + for (int i=mPendingServices.size()-1; i>=0; i--) { + ServiceRecord r = mPendingServices.get(i); + if (r.processName.equals(app.processName) && + r.serviceInfo.applicationInfo.uid == app.info.uid) { + mPendingServices.remove(i); + } + } } // Make sure we have no more records on the stopping list. diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index d75fe5e14894..e2050fc97fe1 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -34,7 +34,6 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; import com.android.internal.app.ProcessStats; -import com.android.internal.app.ResolverActivity; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.ProcessCpuTracker; @@ -218,6 +217,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_IMMERSIVE = localLOGV || false; static final boolean DEBUG_MU = localLOGV || false; static final boolean DEBUG_OOM_ADJ = localLOGV || false; + static final boolean DEBUG_LRU = localLOGV || false; static final boolean DEBUG_PAUSE = localLOGV || false; static final boolean DEBUG_POWER = localLOGV || false; static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; @@ -457,6 +457,23 @@ public final class ActivityManagerService extends ActivityManagerNative final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<Long>(); /** + * Information about a process that is currently marked as bad. + */ + static final class BadProcessInfo { + BadProcessInfo(long time, String shortMsg, String longMsg, String stack) { + this.time = time; + this.shortMsg = shortMsg; + this.longMsg = longMsg; + this.stack = stack; + } + + final long time; + final String shortMsg; + final String longMsg; + final String stack; + } + + /** * Set of applications that we consider to be bad, and will reject * incoming broadcasts from (which the user has no control over). * Processes are added to this set when they have crashed twice within @@ -464,7 +481,7 @@ public final class ActivityManagerService extends ActivityManagerNative * later restarted (hopefully due to some user action). The value is the * time it was added to the list. */ - final ProcessMap<Long> mBadProcesses = new ProcessMap<Long>(); + final ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<BadProcessInfo>(); /** * All of the processes we currently have running organized by pid. @@ -1311,7 +1328,7 @@ public final class ActivityManagerService extends ActivityManagerNative String pkg = bundle.getString("pkg"); String reason = bundle.getString("reason"); forceStopPackageLocked(pkg, appid, restart, false, true, false, - UserHandle.USER_ALL, reason); + false, UserHandle.USER_ALL, reason); } } break; case FINALIZE_PENDING_INTENT_MSG: { @@ -1743,7 +1760,8 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (mSelf.mPidsSelfLocked) { mSelf.mPidsSelfLocked.put(app.pid, app); } - mSelf.updateLruProcessLocked(app, true, false); + mSelf.updateLruProcessLocked(app, false, null); + mSelf.updateOomAdjLocked(); } } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException( @@ -2131,7 +2149,8 @@ public final class ActivityManagerService extends ActivityManagerNative totalUTime += otherUTime; totalSTime += otherSTime; if (pr != null) { - BatteryStatsImpl.Uid.Proc ps = pr.batteryStats; + BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked( + st.name, st.pid); ps.addCpuTimeLocked(st.rel_utime-otherUTime, st.rel_stime-otherSTime); ps.addSpeedStepTimes(cpuSpeedTimes); @@ -2269,7 +2288,7 @@ public final class ActivityManagerService extends ActivityManagerNative int lrui = mLruProcesses.lastIndexOf(app); if (lrui < 0) { - Log.wtf(TAG, "Adding dependent process " + app + " not on LRU list: " + Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: " + what + " " + obj + " from " + srcApp); return index; } @@ -2289,6 +2308,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (index > 0) { index--; } + if (DEBUG_LRU) Slog.d(TAG, "Moving dep from " + lrui + " to " + index + + " in LRU list: " + app); mLruProcesses.add(index, app); return index; } @@ -2306,8 +2327,9 @@ public final class ActivityManagerService extends ActivityManagerNative } } - final void updateLruProcessLocked(ProcessRecord app, boolean oomAdj, boolean activityChange) { - final boolean hasActivity = app.activities.size() > 0; + final void updateLruProcessLocked(ProcessRecord app, boolean activityChange, + ProcessRecord client) { + final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities; final boolean hasService = false; // not impl yet. app.services.size() > 0; if (!activityChange && hasActivity) { // The process has activties, so we are only going to allow activity-based @@ -2321,8 +2343,65 @@ public final class ActivityManagerService extends ActivityManagerNative final long now = SystemClock.uptimeMillis(); app.lastActivityTime = now; + // First a quick reject: if the app is already at the position we will + // put it, then there is nothing to do. + if (hasActivity) { + final int N = mLruProcesses.size(); + if (N > 0 && mLruProcesses.get(N-1) == app) { + if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top activity: " + app); + return; + } + } else { + if (mLruProcessServiceStart > 0 + && mLruProcesses.get(mLruProcessServiceStart-1) == app) { + if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top other: " + app); + return; + } + } + int lrui = mLruProcesses.lastIndexOf(app); + if (app.persistent && lrui >= 0) { + // We don't care about the position of persistent processes, as long as + // they are in the list. + if (DEBUG_LRU) Slog.d(TAG, "Not moving, persistent: " + app); + return; + } + + /* In progress: compute new position first, so we can avoid doing work + if the process is not actually going to move. Not yet working. + int addIndex; + int nextIndex; + boolean inActivity = false, inService = false; + if (hasActivity) { + // Process has activities, put it at the very tipsy-top. + addIndex = mLruProcesses.size(); + nextIndex = mLruProcessServiceStart; + inActivity = true; + } else if (hasService) { + // Process has services, put it at the top of the service list. + addIndex = mLruProcessActivityStart; + nextIndex = mLruProcessServiceStart; + inActivity = true; + inService = true; + } else { + // Process not otherwise of interest, it goes to the top of the non-service area. + addIndex = mLruProcessServiceStart; + if (client != null) { + int clientIndex = mLruProcesses.lastIndexOf(client); + if (clientIndex < 0) Slog.d(TAG, "Unknown client " + client + " when updating " + + app); + if (clientIndex >= 0 && addIndex > clientIndex) { + addIndex = clientIndex; + } + } + nextIndex = addIndex > 0 ? addIndex-1 : addIndex; + } + + Slog.d(TAG, "Update LRU at " + lrui + " to " + addIndex + " (act=" + + mLruProcessActivityStart + "): " + app); + */ + if (lrui >= 0) { if (lrui < mLruProcessActivityStart) { mLruProcessActivityStart--; @@ -2330,23 +2409,91 @@ public final class ActivityManagerService extends ActivityManagerNative if (lrui < mLruProcessServiceStart) { mLruProcessServiceStart--; } + /* + if (addIndex > lrui) { + addIndex--; + } + if (nextIndex > lrui) { + nextIndex--; + } + */ mLruProcesses.remove(lrui); } + /* + mLruProcesses.add(addIndex, app); + if (inActivity) { + mLruProcessActivityStart++; + } + if (inService) { + mLruProcessActivityStart++; + } + */ + int nextIndex; if (hasActivity) { - // Process has activities, put it at the very tipsy-top. - mLruProcesses.add(app); - nextIndex = mLruProcessActivityStart; + final int N = mLruProcesses.size(); + if (app.activities.size() == 0 && mLruProcessActivityStart < (N-1)) { + // Process doesn't have activities, but has clients with + // activities... move it up, but one below the top (the top + // should always have a real activity). + if (DEBUG_LRU) Slog.d(TAG, "Adding to second-top of LRU activity list: " + app); + mLruProcesses.add(N-1, app); + // To keep it from spamming the LRU list (by making a bunch of clients), + // we will push down any other entries owned by the app. + final int uid = app.info.uid; + for (int i=N-2; i>mLruProcessActivityStart; i--) { + ProcessRecord subProc = mLruProcesses.get(i); + if (subProc.info.uid == uid) { + // We want to push this one down the list. If the process after + // it is for the same uid, however, don't do so, because we don't + // want them internally to be re-ordered. + if (mLruProcesses.get(i-1).info.uid != uid) { + if (DEBUG_LRU) Slog.d(TAG, "Pushing uid " + uid + " swapping at " + i + + ": " + mLruProcesses.get(i) + " : " + mLruProcesses.get(i-1)); + ProcessRecord tmp = mLruProcesses.get(i); + mLruProcesses.set(i, mLruProcesses.get(i-1)); + mLruProcesses.set(i-1, tmp); + i--; + } + } else { + // A gap, we can stop here. + break; + } + } + } else { + // Process has activities, put it at the very tipsy-top. + if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU activity list: " + app); + mLruProcesses.add(app); + } + nextIndex = mLruProcessServiceStart; } else if (hasService) { // Process has services, put it at the top of the service list. + if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU service list: " + app); mLruProcesses.add(mLruProcessActivityStart, app); nextIndex = mLruProcessServiceStart; mLruProcessActivityStart++; } else { // Process not otherwise of interest, it goes to the top of the non-service area. - mLruProcesses.add(mLruProcessServiceStart, app); - nextIndex = mLruProcessServiceStart-1; + int index = mLruProcessServiceStart; + if (client != null) { + // If there is a client, don't allow the process to be moved up higher + // in the list than that client. + int clientIndex = mLruProcesses.lastIndexOf(client); + if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG, "Unknown client " + client + + " when updating " + app); + if (clientIndex <= lrui) { + // Don't allow the client index restriction to push it down farther in the + // list than it already is. + clientIndex = lrui; + } + if (clientIndex >= 0 && index > clientIndex) { + index = clientIndex; + } + } + if (DEBUG_LRU) Slog.d(TAG, "Adding at " + index + " of LRU list: " + app); + mLruProcesses.add(index, app); + nextIndex = index-1; mLruProcessActivityStart++; mLruProcessServiceStart++; } @@ -2357,23 +2504,19 @@ public final class ActivityManagerService extends ActivityManagerNative ConnectionRecord cr = app.connections.valueAt(j); if (cr.binding != null && !cr.serviceDead && cr.binding.service != null && cr.binding.service.app != null - && cr.binding.service.app.lruSeq != mLruSeq) { + && cr.binding.service.app.lruSeq != mLruSeq + && !cr.binding.service.app.persistent) { nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex, "service connection", cr, app); } } for (int j=app.conProviders.size()-1; j>=0; j--) { ContentProviderRecord cpr = app.conProviders.get(j).provider; - if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) { + if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.persistent) { nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex, "provider reference", cpr, app); } } - - //Slog.i(TAG, "Putting proc to front: " + app.processName); - if (oomAdj) { - updateOomAdjLocked(); - } } final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) { @@ -2627,10 +2770,10 @@ public final class ActivityManagerService extends ActivityManagerNative app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, null); - BatteryStatsImpl bs = app.batteryStats.getBatteryStats(); + BatteryStatsImpl bs = mBatteryStatsService.getActiveStatistics(); synchronized (bs) { if (bs.isOnBattery()) { - app.batteryStats.incStartsLocked(); + bs.getProcessStatsLocked(app.uid, app.processName).incStartsLocked(); } } @@ -4351,7 +4494,7 @@ public final class ActivityManagerService extends ActivityManagerNative private void forceStopPackageLocked(final String packageName, int uid, String reason) { forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false, - false, true, false, UserHandle.getUserId(uid), reason); + false, true, false, false, UserHandle.getUserId(uid), reason); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, Uri.fromParts("package", packageName, null)); if (!mProcessesReady) { @@ -4367,7 +4510,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private void forceStopUserLocked(int userId, String reason) { - forceStopPackageLocked(null, -1, false, false, true, false, userId, reason); + forceStopPackageLocked(null, -1, false, false, true, false, false, userId, reason); Intent intent = new Intent(Intent.ACTION_USER_STOPPED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); @@ -4452,7 +4595,7 @@ public final class ActivityManagerService extends ActivityManagerNative private final boolean forceStopPackageLocked(String name, int appId, boolean callerWillRestart, boolean purgeCache, boolean doit, - boolean evenPersistent, int userId, String reason) { + boolean evenPersistent, boolean uninstalling, int userId, String reason) { int i; int N; @@ -4544,7 +4687,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Remove transient permissions granted from/to this package/user removeUriPermissionsForPackageLocked(name, userId, false); - if (name == null) { + if (name == null || uninstalling) { // Remove pending intents. For now we only do this when force // stopping users, because we have some problems when doing this // for packages -- app widgets are not currently cleaned up for @@ -4831,7 +4974,7 @@ public final class ActivityManagerService extends ActivityManagerNative isRestrictedBackupMode || !normalMode, app.persistent, new Configuration(mConfiguration), app.compat, getCommonServicesLocked(), mCoreSettingsObserver.getCoreSettingsLocked()); - updateLruProcessLocked(app, false, false); + updateLruProcessLocked(app, false, null); app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis(); } catch (Exception e) { // todo: Yikes! What should we do? For now we will try to @@ -4989,7 +5132,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (pkgs != null) { for (String pkg : pkgs) { synchronized (ActivityManagerService.this) { - if (forceStopPackageLocked(pkg, -1, false, false, false, false, 0, + if (forceStopPackageLocked(pkg, -1, false, false, false, false, false, 0, "finished booting")) { setResultCode(Activity.RESULT_OK); return; @@ -7419,7 +7562,7 @@ public final class ActivityManagerService extends ActivityManagerNative // make sure to count it as being accessed and thus // back up on the LRU list. This is good because // content providers are often expensive to start. - updateLruProcessLocked(cpr.proc, false, false); + updateLruProcessLocked(cpr.proc, false, null); } } @@ -8008,10 +8151,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - synchronized (stats) { - ps = stats.getProcessStatsLocked(info.uid, proc); - } - return new ProcessRecord(ps, info, proc, uid); + return new ProcessRecord(stats, info, proc, uid); } final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) { @@ -8028,7 +8168,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (isolated) { mIsolatedProcesses.put(app.uid, app); } - updateLruProcessLocked(app, true, false); + updateLruProcessLocked(app, false, null); + updateOomAdjLocked(); } // This package really, really can not be stopped. @@ -8304,7 +8445,7 @@ public final class ActivityManagerService extends ActivityManagerNative mDebugTransient = !persistent; if (packageName != null) { forceStopPackageLocked(packageName, -1, false, false, true, true, - UserHandle.USER_ALL, "set debug app"); + false, UserHandle.USER_ALL, "set debug app"); } } } finally { @@ -9084,8 +9225,13 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityInfo ai = ris.get(i).activityInfo; ComponentName comp = new ComponentName(ai.packageName, ai.name); if (lastDoneReceivers.contains(comp)) { + // We already did the pre boot receiver for this app with the current + // platform version, so don't do it again... ris.remove(i); i--; + // ...however, do keep it as one that has been done, so we don't + // forget about it when rewriting the file of last done receivers. + doneReceivers.add(comp); } } @@ -9292,7 +9438,7 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace); startAppProblemLocked(app); app.stopFreezingAllLocked(); - return handleAppCrashLocked(app); + return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace); } private void makeAppNotRespondingLocked(ProcessRecord app, @@ -9347,13 +9493,14 @@ public final class ActivityManagerService extends ActivityManagerNative app.waitDialog = null; } if (app.pid > 0 && app.pid != MY_PID) { - handleAppCrashLocked(app); + handleAppCrashLocked(app, null, null, null); killUnneededProcessLocked(app, "user request after error"); } } } - private boolean handleAppCrashLocked(ProcessRecord app) { + private boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg, + String stackTrace) { if (mHeadless) { Log.e(TAG, "handleAppCrashLocked: " + app.processName); return false; @@ -9383,7 +9530,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (!app.isolated) { // XXX We don't have a way to mark isolated processes // as bad, since they don't have a peristent identity. - mBadProcesses.put(app.info.processName, app.uid, now); + mBadProcesses.put(app.info.processName, app.uid, + new BadProcessInfo(now, shortMsg, longMsg, stackTrace)); mProcessCrashTimes.remove(app.info.processName, app.uid); } app.bad = true; @@ -10101,7 +10249,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.persistent) { outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT; } - if (app.hasActivities) { + if (app.activities.size() > 0) { outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES; } outInfo.lastTrimLevel = app.trimMemoryLevel; @@ -10645,11 +10793,11 @@ public final class ActivityManagerService extends ActivityManagerNative if (mBadProcesses.getMap().size() > 0) { boolean printed = false; - final ArrayMap<String, SparseArray<Long>> pmap = mBadProcesses.getMap(); + final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap(); final int NP = pmap.size(); for (int ip=0; ip<NP; ip++) { String pname = pmap.keyAt(ip); - SparseArray<Long> uids = pmap.valueAt(ip); + SparseArray<BadProcessInfo> uids = pmap.valueAt(ip); final int N = uids.size(); for (int i=0; i<N; i++) { int puid = uids.keyAt(i); @@ -10664,10 +10812,33 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" Bad processes:"); printedAnything = true; } + BadProcessInfo info = uids.valueAt(i); pw.print(" Bad process "); pw.print(pname); pw.print(" uid "); pw.print(puid); - pw.print(": crashed at time "); - pw.println(uids.valueAt(i)); + pw.print(": crashed at time "); pw.println(info.time); + if (info.shortMsg != null) { + pw.print(" Short msg: "); pw.println(info.shortMsg); + } + if (info.longMsg != null) { + pw.print(" Long msg: "); pw.println(info.longMsg); + } + if (info.stack != null) { + pw.println(" Stack:"); + int lastPos = 0; + for (int pos=0; pos<info.stack.length(); pos++) { + if (info.stack.charAt(pos) == '\n') { + pw.print(" "); + pw.write(info.stack, lastPos, pos-lastPos); + pw.println(); + lastPos = pos+1; + } + } + if (lastPos < info.stack.length()) { + pw.print(" "); + pw.write(info.stack, lastPos, info.stack.length()-lastPos); + pw.println(); + } + } } } } @@ -11733,6 +11904,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean dumpDalvik = false; boolean oomOnly = false; boolean isCompact = false; + boolean localOnly = false; int opti = 0; while (opti < args.length) { @@ -11751,12 +11923,15 @@ public final class ActivityManagerService extends ActivityManagerNative isCompact = true; } else if ("--oom".equals(opt)) { oomOnly = true; + } else if ("--local".equals(opt)) { + localOnly = true; } else if ("-h".equals(opt)) { pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]"); pw.println(" -a: include all available information for each process."); pw.println(" -d: include dalvik details when dumping process details."); pw.println(" -c: dump in a compact machine-parseable representation."); pw.println(" --oom: only show processes organized by oom adj."); + pw.println(" --local: only collect details locally, don't call process."); pw.println("If [process] is specified it can be the name or "); pw.println("pid of a specific process to dump."); return; @@ -11857,7 +12032,7 @@ public final class ActivityManagerService extends ActivityManagerNative thread = r.thread; pid = r.pid; oomAdj = r.getSetAdjWithServices(); - hasActivities = r.hasActivities; + hasActivities = r.activities.size() > 0; } if (thread != null) { if (!isCheckinRequest && dumpDetails) { @@ -11873,14 +12048,22 @@ public final class ActivityManagerService extends ActivityManagerNative mi.dalvikPrivateDirty = (int)tmpLong[0]; } if (dumpDetails) { - try { - pw.flush(); - thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails, - dumpDalvik, innerArgs); - } catch (RemoteException e) { - if (!isCheckinRequest) { - pw.println("Got RemoteException!"); + if (localOnly) { + ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails, + dumpDalvik, pid, r.processName, 0, 0, 0, 0, 0, 0); + if (isCheckinRequest) { + pw.println(); + } + } else { + try { pw.flush(); + thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails, + dumpDalvik, innerArgs); + } catch (RemoteException e) { + if (!isCheckinRequest) { + pw.println("Got RemoteException!"); + pw.flush(); + } } } } @@ -13195,7 +13378,7 @@ public final class ActivityManagerService extends ActivityManagerNative String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); if (list != null && (list.length > 0)) { for (String pkg : list) { - forceStopPackageLocked(pkg, -1, false, true, true, false, userId, + forceStopPackageLocked(pkg, -1, false, true, true, false, false, userId, "storage unmount"); } sendPackageBroadcastLocked( @@ -13207,10 +13390,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (data != null && (ssp=data.getSchemeSpecificPart()) != null) { boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals( intent.getAction()); + boolean fullUninstall = removed && + !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) { forceStopPackageLocked(ssp, UserHandle.getAppId( intent.getIntExtra(Intent.EXTRA_UID, -1)), false, true, true, - false, userId, removed ? "pkg removed" : "pkg changed"); + false, fullUninstall, userId, + removed ? "pkg removed" : "pkg changed"); } if (removed) { sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED, @@ -13688,7 +13874,7 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); // Instrumentation can kill and relaunch even persistent processes - forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId, + forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId, "start instr"); ProcessRecord app = addAppLocked(ai, false); app.instrumentationClass = className; @@ -13756,7 +13942,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.instrumentationProfileFile = null; app.instrumentationArguments = null; - forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId, + forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false, app.userId, "finished inst"); } @@ -14079,7 +14265,6 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjTarget = null; app.empty = false; app.cached = false; - app.hasClientActivities = false; final int activitiesSize = app.activities.size(); @@ -14089,7 +14274,6 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "fixed"; app.adjSeq = mAdjSeq; app.curRawAdj = app.maxAdj; - app.hasActivities = false; app.foregroundActivities = false; app.keeping = true; app.curSchedGroup = Process.THREAD_GROUP_DEFAULT; @@ -14101,16 +14285,12 @@ public final class ActivityManagerService extends ActivityManagerNative app.systemNoUi = true; if (app == TOP_APP) { app.systemNoUi = false; - app.hasActivities = true; } else if (activitiesSize > 0) { for (int j = 0; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.visible) { app.systemNoUi = false; } - if (r.app == app) { - app.hasActivities = true; - } } } if (!app.systemNoUi) { @@ -14121,7 +14301,6 @@ public final class ActivityManagerService extends ActivityManagerNative app.keeping = false; app.systemNoUi = false; - app.hasActivities = false; // Determine the importance of the process, starting with most // important to least, and assign an appropriate OOM adjustment. @@ -14138,7 +14317,6 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "top-activity"; foregroundActivities = true; interesting = true; - app.hasActivities = true; procState = ActivityManager.PROCESS_STATE_TOP; } else if (app.instrumentationClass != null) { // Don't want to kill running instrumentation. @@ -14187,7 +14365,6 @@ public final class ActivityManagerService extends ActivityManagerNative + app + "?!?"); continue; } - app.hasActivities = true; if (r.visible) { // App has a visible activity; only upgrade adjustment. if (adj > ProcessList.VISIBLE_APP_ADJ) { @@ -14436,27 +14613,6 @@ public final class ActivityManagerService extends ActivityManagerNative clientAdj = adj; } } - } else if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) { - if ((cr.flags&Context.BIND_NOT_VISIBLE) == 0) { - // If this connection is keeping the service - // created, then we want to try to better follow - // its memory management semantics for activities. - // That is, if it is sitting in the background - // LRU list as a cached process (with activities), - // we don't want the service it is connected to - // to go into the empty LRU and quickly get killed, - // because all we'll do is just end up restarting - // the service. - if (client.hasActivities) { - if (procState > - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT) { - procState = - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; - app.adjType = "cch-client-act"; - } - app.hasClientActivities = true; - } - } } if (adj > clientAdj) { // If this process has recently shown UI, and @@ -14674,6 +14830,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } + if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY && app.hasClientActivities) { + // This is a cached process, but with client activities. Mark it so. + procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; + app.adjType = "cch-client-act"; + } + if (adj == ProcessList.SERVICE_ADJ) { if (doingAll) { app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); @@ -15302,7 +15464,6 @@ public final class ActivityManagerService extends ActivityManagerNative // application processes based on their current state. int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ; int nextCachedAdj = curCachedAdj+1; - int curClientCachedAdj = curCachedAdj+1; int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ; int nextEmptyAdj = curEmptyAdj+2; for (int i=N-1; i>=0; i--) { @@ -15317,11 +15478,15 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.curAdj >= ProcessList.UNKNOWN_ADJ) { switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: // This process is a cached process holding activities... // assign it the next cached value for that type, and then // step that cached level. app.curRawAdj = curCachedAdj; app.curAdj = app.modifyRawOomAdj(curCachedAdj); + if (DEBUG_LRU && false) Slog.d(TAG, "Assigning activity LRU #" + i + + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj + + ")"); if (curCachedAdj != nextCachedAdj) { stepCached++; if (stepCached >= cachedFactor) { @@ -15331,25 +15496,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; } - if (curClientCachedAdj <= curCachedAdj) { - curClientCachedAdj = curCachedAdj + 1; - if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { - curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; - } - } } } break; - case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: - // Special case for cached client processes... just step - // down from after regular cached processes. - app.curRawAdj = curClientCachedAdj; - app.curAdj = app.modifyRawOomAdj(curClientCachedAdj); - curClientCachedAdj++; - if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { - curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; - } - break; default: // For everything else, assign next empty cached process // level and bump that up. Note that this means that @@ -15358,6 +15507,9 @@ public final class ActivityManagerService extends ActivityManagerNative // state is still as a service), which is what we want. app.curRawAdj = curEmptyAdj; app.curAdj = app.modifyRawOomAdj(curEmptyAdj); + if (DEBUG_LRU && false) Slog.d(TAG, "Assigning empty LRU #" + i + + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj + + ")"); if (curEmptyAdj != nextEmptyAdj) { stepEmpty++; if (stepEmpty >= emptyFactor) { diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 44ff3bc7ba0d..596c84d87ab8 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -36,7 +36,6 @@ import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; -import android.os.Trace; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.util.Objects; import com.android.server.Watchdog; @@ -61,16 +60,17 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.util.EventLog; import android.util.Slog; @@ -566,7 +566,7 @@ final class ActivityStack { // Move userId's tasks to the top. int index = mTaskHistory.size(); - for (int i = 0; i < index; ++i) { + for (int i = 0; i < index; ) { TaskRecord task = mTaskHistory.get(i); if (task.userId == userId) { if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() + @@ -574,6 +574,9 @@ final class ActivityStack { mTaskHistory.remove(i); mTaskHistory.add(task); --index; + // Use same value for i. + } else { + ++i; } } if (VALIDATE_TOKENS) { @@ -997,8 +1000,8 @@ final class ActivityStack { if (r.isHomeActivity()) { return true; } - if (!r.finishing && r.visible && r.fullscreen) { - // Passed activity is over a visible fullscreen activity. + if (!r.finishing && r.fullscreen) { + // Passed activity is over a fullscreen activity. return false; } } @@ -1091,6 +1094,7 @@ final class ActivityStack { if (!r.visible) { if (DEBUG_VISBILITY) Slog.v( TAG, "Starting and making visible: " + r); + r.visible = true; mWindowManager.setAppVisibility(r.appToken, true); } if (r != starting) { @@ -1141,7 +1145,7 @@ final class ActivityStack { } else if (isActivityOverHome(r)) { if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r); showHomeBehindStack = true; - behindFullscreen = true; + behindFullscreen = !isHomeStack(); } } else { if (DEBUG_VISBILITY) Slog.v( @@ -1284,17 +1288,7 @@ final class ActivityStack { if (prevTask != null && prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) { if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); if (prevTask == nextTask) { - ArrayList<ActivityRecord> activities = prevTask.mActivities; - final int numActivities = activities.size(); - for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { - final ActivityRecord r = activities.get(activityNdx); - // r is usually the same as next, but what if two activities were launched - // before prev finished? - if (!r.finishing) { - r.frontOfTask = true; - break; - } - } + prevTask.setFrontOfTask(); } else if (prevTask != topTask()) { // This task is going away but it was supposed to return to the home task. // Now the task above it has to return to the home task instead. @@ -1398,7 +1392,7 @@ final class ActivityStack { if (next.app != null && next.app.thread != null) { // No reason to do full oom adj update here; we'll let that // happen whenever it needs to later. - mService.updateLruProcessLocked(next.app, false, true); + mService.updateLruProcessLocked(next.app, true, null); } if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; @@ -1526,8 +1520,9 @@ final class ActivityStack { mResumedActivity = next; next.task.touchActiveTime(); mService.addRecentTaskLocked(next.task); - mService.updateLruProcessLocked(next.app, true, true); + mService.updateLruProcessLocked(next.app, true, null); updateLRUListLocked(next); + mService.updateOomAdjLocked(); // Have the window manager re-evaluate the orientation of // the screen based on the new activity order. @@ -1713,7 +1708,7 @@ final class ActivityStack { mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, - r.userId); + r.userId, r.info.configChanges); if (VALIDATE_TOKENS) { validateAppTokensLocked(); } @@ -1745,9 +1740,9 @@ final class ActivityStack { if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to stack to task " + task, new RuntimeException("here").fillInStackTrace()); task.addActivityToTop(r); + task.setFrontOfTask(); r.putInHistory(); - r.frontOfTask = newTask; if (!isHomeStack() || numActivities() > 0) { // We want to show the starting preview window if we are // switching to a new task, or the next activity's process is @@ -1774,7 +1769,8 @@ final class ActivityStack { r.updateOptionsLocked(options); mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId); + (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId, + r.info.configChanges); boolean doShow = true; if (newTask) { // Even though this activity is starting fresh, we still need @@ -1817,7 +1813,8 @@ final class ActivityStack { // because there is nothing for it to animate on top of. mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId); + (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId, + r.info.configChanges); ActivityOptions.abort(options); } if (VALIDATE_TOKENS) { @@ -1907,26 +1904,38 @@ final class ActivityStack { // bottom of the activity stack. This also keeps it // correctly ordered with any activities we previously // moved. + final ThumbnailHolder newThumbHolder; + final TaskRecord targetTask; final ActivityRecord bottom = !mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ? - mTaskHistory.get(0).mActivities.get(0) : null; + mTaskHistory.get(0).mActivities.get(0) : null; if (bottom != null && target.taskAffinity != null && target.taskAffinity.equals(bottom.task.affinity)) { // If the activity currently at the bottom has the // same task affinity as the one we are moving, // then merge it into the same task. - target.setTask(bottom.task, bottom.thumbHolder, false); + targetTask = bottom.task; + newThumbHolder = bottom.thumbHolder == null ? targetTask : bottom.thumbHolder; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to bottom task " + bottom.task); } else { - target.setTask(createTaskRecord(mStackSupervisor.getNextTaskId(), target.info, - null, false), null, false); - target.task.affinityIntent = target.intent; + targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info, + null, false); + newThumbHolder = targetTask; + targetTask.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to new task " + target.task); } - final TaskRecord targetTask = target.task; + if (clearWhenTaskReset) { + // This is the start of a new sub-task. + if (target.thumbHolder == null) { + target.thumbHolder = new ThumbnailHolder(); + } + } else { + target.thumbHolder = newThumbHolder; + } + final int targetTaskId = targetTask.taskId; mWindowManager.setAppGroupId(target.appToken, targetTaskId); @@ -1947,8 +1956,8 @@ final class ActivityStack { } } if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing activity " + p + " from task=" - + task + " adding to task=" + targetTask, - new RuntimeException("here").fillInStackTrace()); + + task + " adding to task=" + targetTask + + " Callers=" + Debug.getCallers(4)); if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p + " out to target's task " + target.task); p.setTask(targetTask, curThumbHolder, false); @@ -2394,15 +2403,12 @@ final class ActivityStack { final ArrayList<ActivityRecord> activities = r.task.mActivities; final int index = activities.indexOf(r); if (index < (activities.size() - 1)) { - ActivityRecord next = activities.get(index+1); - if (r.frontOfTask) { - // The next activity is now the front of the task. - next.frontOfTask = true; - } + r.task.setFrontOfTask(); if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { // If the caller asked that this activity (and all above it) // be cleared when the task is reset, don't lose that information, // but propagate it up to the next activity. + ActivityRecord next = activities.get(index+1); next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); } } @@ -2781,7 +2787,7 @@ final class ActivityStack { } if (r.app.activities.isEmpty()) { // No longer have activities, so update LRU list and oom adj. - mService.updateLruProcessLocked(r.app, false, false); + mService.updateLruProcessLocked(r.app, false, null); mService.updateOomAdjLocked(); } } @@ -3142,7 +3148,9 @@ final class ActivityStack { final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null; if (task == tr && task.mOnTopOfHome || numTasks <= 1) { - task.mOnTopOfHome = false; + if (task != null) { + task.mOnTopOfHome = false; + } return mStackSupervisor.resumeHomeActivity(null); } diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java index 7650a65324b7..62e134044326 100644 --- a/services/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/java/com/android/server/am/ActivityStackSupervisor.java @@ -905,7 +905,8 @@ public final class ActivityStackSupervisor { if (idx < 0) { app.activities.add(r); } - mService.updateLruProcessLocked(app, true, true); + mService.updateLruProcessLocked(app, true, null); + mService.updateOomAdjLocked(); final ActivityStack stack = r.task.stack; try { @@ -1133,6 +1134,19 @@ public final class ActivityStackSupervisor { resultRecord.removeResultsLocked( sourceRecord, resultWho, requestCode); } + if (sourceRecord.launchedFromUid == callingUid) { + // The new activity is being launched from the same uid as the previous + // activity in the flow, and asking to forward its result back to the + // previous. In this case the activity is serving as a trampoline between + // the two, so we also want to update its launchedFromPackage to be the + // same as the previous activity. Note that this is safe, since we know + // these two packages come from the same uid; the caller could just as + // well have supplied that same package name itself. This specifially + // deals with the case of an intent picker/chooser being launched in the app + // flow to redirect to an activity picked by the user, where we want the final + // activity to consider it to have been launched by the previous app activity. + callingPackage = sourceRecord.launchedFromPackage; + } } if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { @@ -1385,17 +1399,22 @@ public final class ActivityStackSupervisor { launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; } + ActivityInfo newTaskInfo = null; + Intent newTaskIntent = null; final ActivityStack sourceStack; if (sourceRecord != null) { if (sourceRecord.finishing) { // If the source is finishing, we can't further count it as our source. This // is because the task it is associated with may now be empty and on its way out, // so we don't want to blindly throw it in to that task. Instead we will take - // the NEW_TASK flow and try to find a task for it. + // the NEW_TASK flow and try to find a task for it. But save the task information + // so it can be used when creating the new task. if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { Slog.w(TAG, "startActivity called from finishing " + sourceRecord + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + newTaskInfo = sourceRecord.info; + newTaskIntent = sourceRecord.task.intent; } sourceRecord = null; sourceStack = null; @@ -1667,8 +1686,10 @@ public final class ActivityStackSupervisor { targetStack = adjustStackFocus(r); moveHomeStack(targetStack.isHomeStack()); if (reuseTask == null) { - r.setTask(targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true), - null, true); + r.setTask(targetStack.createTaskRecord(getNextTaskId(), + newTaskInfo != null ? newTaskInfo : r.info, + newTaskIntent != null ? newTaskIntent : intent, + true), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + r.task); } else { diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 0dd950e1c566..2d596788a829 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -419,6 +419,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } + public void noteWifiBatchedScanStartedFromSource(WorkSource ws, int csph) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteWifiBatchedScanStartedFromSourceLocked(ws, csph); + } + } + + public void noteWifiBatchedScanStoppedFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteWifiBatchedScanStoppedFromSourceLocked(ws); + } + } + public void noteWifiMulticastEnabledFromSource(WorkSource ws) { enforceCallingPermission(); synchronized (mStats) { diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java index 5e80135ec8e2..bfb667f8aca0 100644 --- a/services/java/com/android/server/am/BroadcastQueue.java +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -27,6 +27,7 @@ import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; @@ -220,7 +221,8 @@ public final class BroadcastQueue { r.curApp = app; app.curReceiver = r; app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER); - mService.updateLruProcessLocked(app, true, false); + mService.updateLruProcessLocked(app, false, null); + mService.updateOomAdjLocked(); // Tell the application to launch this receiver. r.intent.setComponent(r.curComponent); @@ -813,6 +815,26 @@ public final class BroadcastQueue { + " to " + r.curApp + ": process crashing"); skip = true; } + if (!skip) { + boolean isAvailable = false; + try { + isAvailable = AppGlobals.getPackageManager().isPackageAvailable( + info.activityInfo.packageName, + UserHandle.getUserId(info.activityInfo.applicationInfo.uid)); + } catch (Exception e) { + // all such failures mean we skip this receiver + Slog.w(TAG, "Exception getting recipient info for " + + info.activityInfo.packageName, e); + } + if (!isAvailable) { + if (DEBUG_BROADCAST) { + Slog.v(TAG, "Skipping delivery to " + info.activityInfo.packageName + + " / " + info.activityInfo.applicationInfo.uid + + " : package no longer available"); + } + skip = true; + } + } if (skip) { if (DEBUG_BROADCAST) Slog.v(TAG, diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java index 576adc26a579..423e540229db 100644 --- a/services/java/com/android/server/am/ConnectionRecord.java +++ b/services/java/com/android/server/am/ConnectionRecord.java @@ -27,7 +27,7 @@ import java.io.PrintWriter; */ final class ConnectionRecord { final AppBindRecord binding; // The application/service binding. - final ActivityRecord activity; // If non-null, the owning activity. + final ActivityRecord activity; // If non-null, the owning activity. final IServiceConnection conn; // The client connection. final int flags; // Binding options. final int clientLabel; // String resource labeling this client. diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index 486e9163b4fc..217a8d6159a1 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -46,7 +46,7 @@ import java.util.ArrayList; * is currently running. */ final class ProcessRecord { - final BatteryStatsImpl.Uid.Proc batteryStats; // where to collect runtime statistics + private final BatteryStatsImpl mBatteryStats; // where to collect runtime statistics final ApplicationInfo info; // all about the first app in the process final boolean isolated; // true if this is a special isolated process final int uid; // uid of process; may be different from 'info' if isolated @@ -86,7 +86,6 @@ final class ProcessRecord { boolean keeping; // Actively running code so don't kill due to that? boolean setIsForeground; // Running foreground UI when last set? boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle? - boolean hasActivities; // Are there any activities running in this process? boolean hasClientActivities; // Are there any client services with activities? boolean hasStartedServices; // Are there any started services running in this process? boolean foregroundServices; // Running any services that are foreground? @@ -265,9 +264,8 @@ final class ProcessRecord { pw.print(prefix); pw.print("persistent="); pw.print(persistent); pw.print(" removed="); pw.println(removed); } - if (hasActivities || hasClientActivities || foregroundActivities) { - pw.print(prefix); pw.print("hasActivities="); pw.print(hasActivities); - pw.print(" hasClientActivities="); pw.print(hasClientActivities); + if (hasClientActivities || foregroundActivities) { + pw.print(prefix); pw.print("hasClientActivities="); pw.print(hasClientActivities); pw.print(" foregroundActivities="); pw.println(foregroundActivities); } if (hasStartedServices) { @@ -275,8 +273,8 @@ final class ProcessRecord { } if (!keeping) { long wtime; - synchronized (batteryStats.getBatteryStats()) { - wtime = batteryStats.getBatteryStats().getProcessWakeTime(info.uid, + synchronized (mBatteryStats) { + wtime = mBatteryStats.getProcessWakeTime(info.uid, pid, SystemClock.elapsedRealtime()); } long timeUsed = wtime - lastWakeTime; @@ -361,9 +359,9 @@ final class ProcessRecord { } } - ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, ApplicationInfo _info, + ProcessRecord(BatteryStatsImpl _batteryStats, ApplicationInfo _info, String _processName, int _uid) { - batteryStats = _batteryStats; + mBatteryStats = _batteryStats; info = _info; isolated = _info.uid != _uid; uid = _uid; diff --git a/services/java/com/android/server/am/ProcessStatsService.java b/services/java/com/android/server/am/ProcessStatsService.java index 8d168807bbd8..e05fcda70ff0 100644 --- a/services/java/com/android/server/am/ProcessStatsService.java +++ b/services/java/com/android/server/am/ProcessStatsService.java @@ -750,23 +750,12 @@ public final class ProcessStatsService extends IProcessStats.Stub { return; } else { // Not an option, last argument must be a package name. - try { - IPackageManager pm = AppGlobals.getPackageManager(); - if (pm.getPackageUid(arg, UserHandle.getCallingUserId()) >= 0) { - reqPackage = arg; - // Include all details, since we know we are only going to - // be dumping a smaller set of data. In fact only the details - // container per-package data, so that are needed to be able - // to dump anything at all when filtering by package. - dumpDetails = true; - } - } catch (RemoteException e) { - } - if (reqPackage == null) { - pw.println("Unknown package: " + arg); - dumpHelp(pw); - return; - } + reqPackage = arg; + // Include all details, since we know we are only going to + // be dumping a smaller set of data. In fact only the details + // container per-package data, so that are needed to be able + // to dump anything at all when filtering by package. + dumpDetails = true; } } } @@ -816,13 +805,14 @@ public final class ProcessStatsService extends IProcessStats.Stub { } return; } else if (aggregateHours != 0) { + pw.print("AGGREGATED OVER LAST "); pw.print(aggregateHours); pw.println(" HOURS:"); dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact, dumpDetails, dumpFullDetails, dumpAll, activeOnly); return; } boolean sepNeeded = false; - if (!currentOnly || isCheckin) { + if (dumpAll || isCheckin) { mWriteLock.lock(); try { ArrayList<String> files = getCommittedFiles(0, false, !isCheckin); @@ -882,11 +872,11 @@ public final class ProcessStatsService extends IProcessStats.Stub { } } if (!isCheckin) { - if (dumpAll) { + if (!currentOnly) { if (sepNeeded) { pw.println(); - pw.println("AGGREGATED OVER LAST 24 HOURS:"); } + pw.println("AGGREGATED OVER LAST 24 HOURS:"); dumpAggregatedStats(pw, 24, now, reqPackage, isCompact, dumpDetails, dumpFullDetails, dumpAll, activeOnly); pw.println(); @@ -901,8 +891,8 @@ public final class ProcessStatsService extends IProcessStats.Stub { } else { if (sepNeeded) { pw.println(); - pw.println("CURRENT STATS:"); } + pw.println("CURRENT STATS:"); if (dumpDetails || dumpFullDetails) { mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, activeOnly); diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index cc1172a8cccd..80e6e9497ece 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -85,6 +85,7 @@ final class ServiceRecord extends Binder { ProcessRecord app; // where this service is running or null. ProcessRecord isolatedProc; // keep track of isolated process, if requested ProcessStats.ServiceState tracker; // tracking service execution, may be null + ProcessStats.ServiceState restartTracker; // tracking service restart boolean delayed; // are we waiting to start this service in the background? boolean isForeground; // is service currently in foreground mode? int foregroundId; // Notification ID of last foreground req. @@ -340,6 +341,19 @@ final class ServiceRecord extends Binder { } } + public void makeRestarting(int memFactor, long now) { + if (restartTracker == null) { + if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) { + restartTracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName, + serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name); + } + if (restartTracker == null) { + return; + } + } + restartTracker.setRestarting(true, memFactor, now); + } + public AppBindRecord retrieveAppBindingLocked(Intent intent, ProcessRecord app) { Intent.FilterComparison filter = new Intent.FilterComparison(intent); diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index 3d568ffb8e07..9105103ac632 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -159,18 +159,33 @@ final class TaskRecord extends ThumbnailHolder { return null; } + /** Call after activity movement or finish to make sure that frontOfTask is set correctly */ + final void setFrontOfTask() { + boolean foundFront = false; + final int numActivities = mActivities.size(); + for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { + final ActivityRecord r = mActivities.get(activityNdx); + if (foundFront || r.finishing) { + r.frontOfTask = false; + } else { + r.frontOfTask = true; + // Set frontOfTask false for every following activity. + foundFront = true; + } + } + } + /** - * Reorder the history stack so that the activity at the given index is - * brought to the front. + * Reorder the history stack so that the passed activity is brought to the front. */ final void moveActivityToFrontLocked(ActivityRecord newTop) { if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + newTop + " to stack at top", new RuntimeException("here").fillInStackTrace()); - getTopActivity().frontOfTask = false; mActivities.remove(newTop); mActivities.add(newTop); - newTop.frontOfTask = true; + + setFrontOfTask(); } void addActivityAtBottom(ActivityRecord r) { diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index f5a7039a1ab6..8f25860172de 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -45,6 +45,7 @@ import android.net.LinkProperties; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.net.NetworkInfo; +import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.NetworkInfo.DetailedState; import android.os.Binder; @@ -77,6 +78,7 @@ import com.android.server.net.BaseNetworkObserver; import java.io.File; import java.io.InputStream; import java.io.OutputStream; +import java.net.InetAddress; import java.net.Inet4Address; import java.net.InetAddress; import java.nio.charset.StandardCharsets; @@ -282,13 +284,12 @@ public class Vpn extends BaseNetworkStateTracker { } /** - * Protect a socket from routing changes by binding it to the given - * interface. The socket is NOT closed by this method. + * Protect a socket from VPN rules by binding it to the main routing table. + * The socket is NOT closed by this method. * * @param socket The socket to be bound. - * @param interfaze The name of the interface. */ - public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception { + public void protect(ParcelFileDescriptor socket) throws Exception { PackageManager pm = mContext.getPackageManager(); int appUid = pm.getPackageUid(mPackage, mUserId); @@ -302,8 +303,6 @@ public class Vpn extends BaseNetworkStateTracker { } finally { Binder.restoreCallingIdentity(token); } - // bind the socket to the interface - jniProtect(socket.getFd(), interfaze); } @@ -353,6 +352,12 @@ public class Vpn extends BaseNetworkStateTracker { Binder.restoreCallingIdentity(token); } + // Save the old config in case we need to go back. + VpnConfig oldConfig = mConfig; + String oldInterface = mInterface; + Connection oldConnection = mConnection; + SparseBooleanArray oldUsers = mVpnUsers; + // Configure the interface. Abort if any of these steps fails. ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); try { @@ -372,12 +377,7 @@ public class Vpn extends BaseNetworkStateTracker { new UserHandle(mUserId))) { throw new IllegalStateException("Cannot bind " + config.user); } - if (mConnection != null) { - mContext.unbindService(mConnection); - } - if (mInterface != null && !mInterface.equals(interfaze)) { - jniReset(mInterface); - } + mConnection = connection; mInterface = interfaze; @@ -386,55 +386,91 @@ public class Vpn extends BaseNetworkStateTracker { config.interfaze = mInterface; config.startTime = SystemClock.elapsedRealtime(); mConfig = config; + // Set up forwarding and DNS rules. mVpnUsers = new SparseBooleanArray(); token = Binder.clearCallingIdentity(); try { mCallback.setMarkedForwarding(mInterface); - mCallback.setRoutes(interfaze, config.routes); + mCallback.setRoutes(mInterface, config.routes); mCallback.override(mInterface, config.dnsServers, config.searchDomains); addVpnUserLocked(mUserId); - + // If we are owner assign all Restricted Users to this VPN + if (mUserId == UserHandle.USER_OWNER) { + for (UserInfo user : mgr.getUsers()) { + if (user.isRestricted()) { + try { + addVpnUserLocked(user.id); + } catch (Exception e) { + Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN"); + } + } + } + } } finally { Binder.restoreCallingIdentity(token); } + if (oldConnection != null) { + mContext.unbindService(oldConnection); + } + if (oldInterface != null && !oldInterface.equals(interfaze)) { + // Remove the old tun's user forwarding rules + // The new tun's user rules have already been added so they will take over + // as rules are deleted. This prevents data leakage as the rules are moved over. + token = Binder.clearCallingIdentity(); + try { + final int size = oldUsers.size(); + final boolean forwardDns = (oldConfig.dnsServers != null && + oldConfig.dnsServers.size() != 0); + for (int i = 0; i < size; i++) { + int user = oldUsers.keyAt(i); + mCallback.clearUserForwarding(oldInterface, user, forwardDns); + } + mCallback.clearMarkedForwarding(oldInterface); + } finally { + Binder.restoreCallingIdentity(token); + } + jniReset(oldInterface); + } } catch (RuntimeException e) { updateState(DetailedState.FAILED, "establish"); IoUtils.closeQuietly(tun); // make sure marked forwarding is cleared if it was set + token = Binder.clearCallingIdentity(); try { mCallback.clearMarkedForwarding(mInterface); } catch (Exception ingored) { // ignored + } finally { + Binder.restoreCallingIdentity(token); } + // restore old state + mConfig = oldConfig; + mConnection = oldConnection; + mVpnUsers = oldUsers; + mInterface = oldInterface; throw e; } Log.i(TAG, "Established by " + config.user + " on " + mInterface); - - // If we are owner assign all Restricted Users to this VPN - if (mUserId == UserHandle.USER_OWNER) { - token = Binder.clearCallingIdentity(); - try { - for (UserInfo user : mgr.getUsers()) { - if (user.isRestricted()) { - try { - addVpnUserLocked(user.id); - } catch (Exception e) { - Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN"); - } - } - } - } finally { - Binder.restoreCallingIdentity(token); - } - } // TODO: ensure that contract class eventually marks as connected updateState(DetailedState.AUTHENTICATING, "establish"); return tun; } + /** + * Check if a given address is covered by the VPN's routing rules. + */ + public boolean isAddressCovered(InetAddress address) { + synchronized (Vpn.this) { + if (!isRunningLocked()) { + return false; + } + return RouteInfo.selectBestRoute(mConfig.routes, address) != null; + } + } + private boolean isRunningLocked() { return mVpnUsers != null; } @@ -670,7 +706,6 @@ public class Vpn extends BaseNetworkStateTracker { private native int jniSetRoutes(String interfaze, String routes); private native void jniReset(String interfaze); private native int jniCheck(String interfaze); - private native void jniProtect(int socket, String interfaze); private static RouteInfo findIPv4DefaultRoute(LinkProperties prop) { for (RouteInfo route : prop.getAllRoutes()) { diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java index cb35ef11c6f6..023bf2b20614 100644 --- a/services/java/com/android/server/content/ContentService.java +++ b/services/java/com/android/server/content/ContentService.java @@ -660,7 +660,7 @@ public final class ContentService extends IContentService.Stub { int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { - return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId); + return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId); } finally { restoreCallingIdentity(identityToken); } diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java index 71d8d9911f9a..9e3dad6ee500 100644 --- a/services/java/com/android/server/content/SyncManager.java +++ b/services/java/com/android/server/content/SyncManager.java @@ -2352,7 +2352,7 @@ public class SyncManager { Log.v(TAG, "canceling and rescheduling sync since an initialization " + "takes higher priority, " + conflict); } - } else if (candidate.expedited && !conflict.mSyncOperation.expedited + } else if (candidate.isExpedited() && !conflict.mSyncOperation.isExpedited() && (candidateIsInitialization == conflict.mSyncOperation.isInitialization())) { toReschedule = conflict; diff --git a/services/java/com/android/server/content/SyncOperation.java b/services/java/com/android/server/content/SyncOperation.java index 485674782aeb..67e3b098f37c 100644 --- a/services/java/com/android/server/content/SyncOperation.java +++ b/services/java/com/android/server/content/SyncOperation.java @@ -66,7 +66,8 @@ public class SyncOperation implements Comparable { public final boolean allowParallelSyncs; public Bundle extras; public final String key; - public boolean expedited; + /** Internal boolean to avoid reading a bundle everytime we want to compare operations. */ + private final boolean expedited; public SyncStorageEngine.PendingOperation pendingOperation; /** Elapsed real time in millis at which to run this sync. */ public long latestRunTime; @@ -79,7 +80,7 @@ public class SyncOperation implements Comparable { * Depends on max(backoff, latestRunTime, and delayUntil). */ public long effectiveRunTime; - /** Amount of time before {@link effectiveRunTime} from which this sync can run. */ + /** Amount of time before {@link #effectiveRunTime} from which this sync can run. */ public long flexTime; public SyncOperation(Account account, int userId, int reason, int source, String authority, @@ -98,11 +99,16 @@ public class SyncOperation implements Comparable { this.backoff = backoff; final long now = SystemClock.elapsedRealtime(); // Checks the extras bundle. Must occur after we set the internal bundle. - if (runTimeFromNow < 0 || isExpedited()) { + if (runTimeFromNow < 0) { + // Sanity check: Will always be true. + if (!this.extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { + this.extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + } this.expedited = true; this.latestRunTime = now; this.flexTime = 0; } else { + this.extras.remove(ContentResolver.SYNC_EXTRAS_EXPEDITED); this.expedited = false; this.latestRunTime = now + runTimeFromNow; this.flexTime = flexTime; @@ -111,6 +117,24 @@ public class SyncOperation implements Comparable { this.key = toKey(); } + /** Only used to immediately reschedule a sync. */ + SyncOperation(SyncOperation other) { + this.service = other.service; + this.account = other.account; + this.authority = other.authority; + this.userId = other.userId; + this.reason = other.reason; + this.syncSource = other.syncSource; + this.extras = new Bundle(other.extras); + this.expedited = other.expedited; + this.latestRunTime = SystemClock.elapsedRealtime(); + this.flexTime = 0L; + this.backoff = other.backoff; + this.allowParallelSyncs = other.allowParallelSyncs; + this.updateEffectiveRunTime(); + this.key = toKey(); + } + /** * Make sure the bundle attached to this SyncOperation doesn't have unnecessary * flags set. @@ -138,24 +162,6 @@ public class SyncOperation implements Comparable { } } - /** Only used to immediately reschedule a sync. */ - SyncOperation(SyncOperation other) { - this.service = other.service; - this.account = other.account; - this.authority = other.authority; - this.userId = other.userId; - this.reason = other.reason; - this.syncSource = other.syncSource; - this.extras = new Bundle(other.extras); - this.expedited = other.expedited; - this.latestRunTime = SystemClock.elapsedRealtime(); - this.flexTime = 0L; - this.backoff = other.backoff; - this.allowParallelSyncs = other.allowParallelSyncs; - this.updateEffectiveRunTime(); - this.key = toKey(); - } - @Override public String toString() { return dump(null, true); @@ -220,7 +226,7 @@ public class SyncOperation implements Comparable { } public boolean isExpedited() { - return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false) || expedited; + return expedited; } public boolean ignoreBackoff() { diff --git a/services/java/com/android/server/content/SyncQueue.java b/services/java/com/android/server/content/SyncQueue.java index 6f3fe6e1d37b..22fa2de6c5d6 100644 --- a/services/java/com/android/server/content/SyncQueue.java +++ b/services/java/com/android/server/content/SyncQueue.java @@ -73,10 +73,9 @@ public class SyncQueue { } SyncOperation syncOperation = new SyncOperation( op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras, - 0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0, + op.expedited ? -1: 0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0, mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority), syncAdapterInfo.type.allowParallelSyncs()); - syncOperation.expedited = op.expedited; syncOperation.pendingOperation = op; add(syncOperation, op); } @@ -104,7 +103,6 @@ public class SyncQueue { if (existingOperation != null) { boolean changed = false; if (operation.compareTo(existingOperation) <= 0 ) { - existingOperation.expedited = operation.expedited; long newRunTime = Math.min(existingOperation.latestRunTime, operation.latestRunTime); // Take smaller runtime. @@ -123,7 +121,7 @@ public class SyncQueue { if (operation.pendingOperation == null) { pop = new SyncStorageEngine.PendingOperation( operation.account, operation.userId, operation.reason, operation.syncSource, - operation.authority, operation.extras, operation.expedited); + operation.authority, operation.extras, operation.isExpedited()); pop = mSyncStorageEngine.insertIntoPending(pop); if (pop == null) { throw new IllegalStateException("error adding pending sync operation " diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java index 41ef22959be5..781280ed69bc 100644 --- a/services/java/com/android/server/content/SyncStorageEngine.java +++ b/services/java/com/android/server/content/SyncStorageEngine.java @@ -501,7 +501,7 @@ public class SyncStorageEngine extends Handler { * @return amount of seconds before syncTimeSeconds that the sync can occur. * I.e. * earliest_sync_time = syncTimeSeconds - calculateDefaultFlexTime(syncTimeSeconds) - * The flex time is capped at a percentage of the {@link DEFAULT_POLL_FREQUENCY_SECONDS}. + * The flex time is capped at a percentage of the {@link #DEFAULT_POLL_FREQUENCY_SECONDS}. */ public static long calculateDefaultFlexTime(long syncTimeSeconds) { if (syncTimeSeconds < DEFAULT_MIN_FLEX_ALLOWED_SECS) { @@ -1295,20 +1295,40 @@ public class SyncStorageEngine extends Handler { } /** - * Return a list of the currently active syncs. Note that the returned items are the - * real, live active sync objects, so be careful what you do with it. + * Return a list of the currently active syncs. Note that the returned + * items are the real, live active sync objects, so be careful what you do + * with it. */ - public List<SyncInfo> getCurrentSyncs(int userId) { + private List<SyncInfo> getCurrentSyncs(int userId) { synchronized (mAuthorities) { - ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId); - if (syncs == null) { - syncs = new ArrayList<SyncInfo>(); - mCurrentSyncs.put(userId, syncs); + return getCurrentSyncsLocked(userId); + } + } + + /** + * @return a copy of the current syncs data structure. Will not return + * null. + */ + public List<SyncInfo> getCurrentSyncsCopy(int userId) { + synchronized (mAuthorities) { + final List<SyncInfo> syncs = getCurrentSyncsLocked(userId); + final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>(); + for (SyncInfo sync : syncs) { + syncsCopy.add(new SyncInfo(sync)); } - return syncs; + return syncsCopy; } } + private List<SyncInfo> getCurrentSyncsLocked(int userId) { + ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId); + if (syncs == null) { + syncs = new ArrayList<SyncInfo>(); + mCurrentSyncs.put(userId, syncs); + } + return syncs; + } + /** * Return an array of the current sync status for all authorities. Note * that the objects inside the array are the real, live status objects, diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java index 249c8b004e64..bcb677fa455f 100644 --- a/services/java/com/android/server/display/DisplayManagerService.java +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -35,6 +35,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -173,6 +174,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub { // The Wifi display adapter, or null if not registered. private WifiDisplayAdapter mWifiDisplayAdapter; + // The number of active wifi display scan requests. + private int mWifiDisplayScanRequestCount; + // The virtual display adapter, or null if not registered. private VirtualDisplayAdapter mVirtualDisplayAdapter; @@ -458,38 +462,94 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } } - private void onCallbackDied(int pid) { + private void onCallbackDied(CallbackRecord record) { synchronized (mSyncRoot) { - mCallbacks.remove(pid); + mCallbacks.remove(record.mPid); + stopWifiDisplayScanLocked(record); } } @Override // Binder call - public void scanWifiDisplays() { + public void startWifiDisplayScan() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to start wifi display scans"); + + final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { + CallbackRecord record = mCallbacks.get(callingPid); + if (record == null) { + throw new IllegalStateException("The calling process has not " + + "registered an IDisplayManagerCallback."); + } + startWifiDisplayScanLocked(record); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void startWifiDisplayScanLocked(CallbackRecord record) { + if (!record.mWifiDisplayScanRequested) { + record.mWifiDisplayScanRequested = true; + if (mWifiDisplayScanRequestCount++ == 0) { if (mWifiDisplayAdapter != null) { - mWifiDisplayAdapter.requestScanLocked(); + mWifiDisplayAdapter.requestStartScanLocked(); + } + } + } + } + + @Override // Binder call + public void stopWifiDisplayScan() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to stop wifi display scans"); + + final int callingPid = Binder.getCallingPid(); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + CallbackRecord record = mCallbacks.get(callingPid); + if (record == null) { + throw new IllegalStateException("The calling process has not " + + "registered an IDisplayManagerCallback."); } + stopWifiDisplayScanLocked(record); } } finally { Binder.restoreCallingIdentity(token); } } + private void stopWifiDisplayScanLocked(CallbackRecord record) { + if (record.mWifiDisplayScanRequested) { + record.mWifiDisplayScanRequested = false; + if (--mWifiDisplayScanRequestCount == 0) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestStopScanLocked(); + } + } else if (mWifiDisplayScanRequestCount < 0) { + Log.wtf(TAG, "mWifiDisplayScanRequestCount became negative: " + + mWifiDisplayScanRequestCount); + mWifiDisplayScanRequestCount = 0; + } + } + } + @Override // Binder call public void connectWifiDisplay(String address) { if (address == null) { throw new IllegalArgumentException("address must not be null"); } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to connect to a wifi display"); - final boolean trusted = canCallerConfigureWifiDisplay(); final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { if (mWifiDisplayAdapter != null) { - mWifiDisplayAdapter.requestConnectLocked(address, trusted); + mWifiDisplayAdapter.requestConnectLocked(address); } } } finally { @@ -499,12 +559,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override public void pauseWifiDisplay() { - if (mContext.checkCallingPermission( - android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY" - + "permission to pause a wifi display session."); - } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to pause a wifi display session"); final long token = Binder.clearCallingIdentity(); try { @@ -520,12 +576,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override public void resumeWifiDisplay() { - if (mContext.checkCallingPermission( - android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY" - + "permission to resume a wifi display session."); - } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to resume a wifi display session"); final long token = Binder.clearCallingIdentity(); try { @@ -541,6 +593,11 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override // Binder call public void disconnectWifiDisplay() { + // This request does not require special permissions. + // Any app can request disconnection from the currently active wifi display. + // This exception should no longer be needed once wifi display control moves + // to the media router service. + final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { @@ -558,10 +615,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub { if (address == null) { throw new IllegalArgumentException("address must not be null"); } - if (!canCallerConfigureWifiDisplay()) { - throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission to " - + "rename a wifi display."); - } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to rename to a wifi display"); final long token = Binder.clearCallingIdentity(); try { @@ -580,10 +635,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub { if (address == null) { throw new IllegalArgumentException("address must not be null"); } - if (!canCallerConfigureWifiDisplay()) { - throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission to " - + "forget a wifi display."); - } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to forget to a wifi display"); final long token = Binder.clearCallingIdentity(); try { @@ -599,6 +652,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override // Binder call public WifiDisplayStatus getWifiDisplayStatus() { + // This request does not require special permissions. + // Any app can get information about available wifi displays. + final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { @@ -612,11 +668,6 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } } - private boolean canCallerConfigureWifiDisplay() { - return mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) - == PackageManager.PERMISSION_GRANTED; - } - @Override // Binder call public int createVirtualDisplay(IBinder appToken, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags) { @@ -1112,6 +1163,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub { pw.println(" mDefaultViewport=" + mDefaultViewport); pw.println(" mExternalTouchViewport=" + mExternalTouchViewport); pw.println(" mSingleDisplayDemoMode=" + mSingleDisplayDemoMode); + pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount); IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); ipw.increaseIndent(); @@ -1139,6 +1191,15 @@ public final class DisplayManagerService extends IDisplayManager.Stub { pw.println(" Display " + displayId + ":"); display.dumpLocked(ipw); } + + final int callbackCount = mCallbacks.size(); + pw.println(); + pw.println("Callbacks: size=" + callbackCount); + for (int i = 0; i < callbackCount; i++) { + CallbackRecord callback = mCallbacks.valueAt(i); + pw.println(" " + i + ": mPid=" + callback.mPid + + ", mWifiDisplayScanRequested=" + callback.mWifiDisplayScanRequested); + } } } @@ -1239,9 +1300,11 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } private final class CallbackRecord implements DeathRecipient { - private final int mPid; + public final int mPid; private final IDisplayManagerCallback mCallback; + public boolean mWifiDisplayScanRequested; + public CallbackRecord(int pid, IDisplayManagerCallback callback) { mPid = pid; mCallback = callback; @@ -1252,7 +1315,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub { if (DEBUG) { Slog.d(TAG, "Display listener for pid " + mPid + " died."); } - onCallbackDied(mPid); + onCallbackDied(this); } public void notifyDisplayEventAsync(int displayId, int event) { diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java index f7bbdf8246bb..cd57941bcc3e 100644 --- a/services/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/java/com/android/server/display/WifiDisplayAdapter.java @@ -127,7 +127,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast); pw.println("mPendingNotificationUpdate=" + mPendingNotificationUpdate); pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers); - + // Try to dump the controller state. if (mDisplayController == null) { pw.println("mDisplayController=null"); @@ -157,34 +157,39 @@ final class WifiDisplayAdapter extends DisplayAdapter { }); } - public void requestScanLocked() { + public void requestStartScanLocked() { if (DEBUG) { - Slog.d(TAG, "requestScanLocked"); + Slog.d(TAG, "requestStartScanLocked"); } getHandler().post(new Runnable() { @Override public void run() { if (mDisplayController != null) { - mDisplayController.requestScan(); + mDisplayController.requestStartScan(); } } }); } - public void requestConnectLocked(final String address, final boolean trusted) { + public void requestStopScanLocked() { if (DEBUG) { - Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted); + Slog.d(TAG, "requestStopScanLocked"); } - if (!trusted) { - synchronized (getSyncRoot()) { - if (!isRememberedDisplayLocked(address)) { - Slog.w(TAG, "Ignoring request by an untrusted client to connect to " - + "an unknown wifi display: " + address); - return; + getHandler().post(new Runnable() { + @Override + public void run() { + if (mDisplayController != null) { + mDisplayController.requestStopScan(); } } + }); + } + + public void requestConnectLocked(final String address) { + if (DEBUG) { + Slog.d(TAG, "requestConnectLocked: address=" + address); } getHandler().post(new Runnable() { @@ -197,15 +202,6 @@ final class WifiDisplayAdapter extends DisplayAdapter { }); } - private boolean isRememberedDisplayLocked(String address) { - for (WifiDisplay display : mRememberedDisplays) { - if (display.getDeviceAddress().equals(address)) { - return true; - } - } - return false; - } - public void requestPauseLocked() { if (DEBUG) { Slog.d(TAG, "requestPauseLocked"); @@ -400,8 +396,6 @@ final class WifiDisplayAdapter extends DisplayAdapter { mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height, refreshRate, deviceFlags, address, surface); sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED); - - scheduleUpdateNotificationLocked(); } private void removeDisplayDeviceLocked() { @@ -409,8 +403,6 @@ final class WifiDisplayAdapter extends DisplayAdapter { mDisplayDevice.destroyLocked(); sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED); mDisplayDevice = null; - - scheduleUpdateNotificationLocked(); } } @@ -457,21 +449,24 @@ final class WifiDisplayAdapter extends DisplayAdapter { // Runs on the handler. private void handleUpdateNotification() { - final boolean isConnected; + final int state; + final WifiDisplay display; synchronized (getSyncRoot()) { if (!mPendingNotificationUpdate) { return; } mPendingNotificationUpdate = false; - isConnected = (mDisplayDevice != null); + state = mActiveDisplayState; + display = mActiveDisplay; } // Cancel the old notification if there is one. mNotificationManager.cancelAsUser(null, - R.string.wifi_display_notification_title, UserHandle.ALL); + R.string.wifi_display_notification_disconnect, UserHandle.ALL); - if (isConnected) { + if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING + || state == WifiDisplayStatus.DISPLAY_STATE_CONNECTED) { Context context = getContext(); // Initialize pending intents for the notification outside of the lock because @@ -493,20 +488,38 @@ final class WifiDisplayAdapter extends DisplayAdapter { // Post the notification. Resources r = context.getResources(); - Notification notification = new Notification.Builder(context) - .setContentTitle(r.getString( - R.string.wifi_display_notification_title)) - .setContentText(r.getString( - R.string.wifi_display_notification_message)) - .setContentIntent(mSettingsPendingIntent) - .setSmallIcon(R.drawable.ic_notify_wifidisplay) - .setOngoing(true) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, - r.getString(R.string.wifi_display_notification_disconnect), - mDisconnectPendingIntent) - .build(); + Notification notification; + if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING) { + notification = new Notification.Builder(context) + .setContentTitle(r.getString( + R.string.wifi_display_notification_connecting_title)) + .setContentText(r.getString( + R.string.wifi_display_notification_connecting_message, + display.getFriendlyDisplayName())) + .setContentIntent(mSettingsPendingIntent) + .setSmallIcon(R.drawable.ic_notification_cast_connecting) + .setOngoing(true) + .addAction(android.R.drawable.ic_menu_close_clear_cancel, + r.getString(R.string.wifi_display_notification_disconnect), + mDisconnectPendingIntent) + .build(); + } else { + notification = new Notification.Builder(context) + .setContentTitle(r.getString( + R.string.wifi_display_notification_connected_title)) + .setContentText(r.getString( + R.string.wifi_display_notification_connected_message, + display.getFriendlyDisplayName())) + .setContentIntent(mSettingsPendingIntent) + .setSmallIcon(R.drawable.ic_notification_cast_on) + .setOngoing(true) + .addAction(android.R.drawable.ic_menu_close_clear_cancel, + r.getString(R.string.wifi_display_notification_disconnect), + mDisconnectPendingIntent) + .build(); + } mNotificationManager.notifyAsUser(null, - R.string.wifi_display_notification_title, + R.string.wifi_display_notification_disconnect, notification, UserHandle.ALL); } } @@ -545,20 +558,20 @@ final class WifiDisplayAdapter extends DisplayAdapter { } @Override - public void onScanFinished(WifiDisplay[] availableDisplays) { + public void onScanResults(WifiDisplay[] availableDisplays) { synchronized (getSyncRoot()) { availableDisplays = mPersistentDataStore.applyWifiDisplayAliases( availableDisplays); - // check if any of the available displays changed canConnect status boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays); + + // Check whether any of the available displays changed canConnect status. for (int i = 0; !changed && i<availableDisplays.length; i++) { changed = availableDisplays[i].canConnect() != mAvailableDisplays[i].canConnect(); } - if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING || changed) { - mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING; + if (changed) { mAvailableDisplays = availableDisplays; fixRememberedDisplayNamesFromAvailableDisplaysLocked(); updateDisplaysLocked(); @@ -568,6 +581,16 @@ final class WifiDisplayAdapter extends DisplayAdapter { } @Override + public void onScanFinished() { + synchronized (getSyncRoot()) { + if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING) { + mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING; + scheduleStatusChangedBroadcastLocked(); + } + } + } + + @Override public void onDisplayConnecting(WifiDisplay display) { synchronized (getSyncRoot()) { display = mPersistentDataStore.applyWifiDisplayAlias(display); @@ -578,6 +601,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING; mActiveDisplay = display; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -590,6 +614,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED; mActiveDisplay = null; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -607,6 +632,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED; mActiveDisplay = display; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -629,6 +655,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplay = display; renameDisplayDeviceLocked(display.getFriendlyDisplayName()); scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -644,6 +671,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED; mActiveDisplay = null; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java index 9a4cfb77775a..dbb59b269bf5 100644 --- a/services/java/com/android/server/display/WifiDisplayController.java +++ b/services/java/com/android/server/display/WifiDisplayController.java @@ -27,7 +27,6 @@ import android.database.ContentObserver; import android.hardware.display.WifiDisplay; import android.hardware.display.WifiDisplaySessionInfo; import android.hardware.display.WifiDisplayStatus; -import android.media.AudioManager; import android.media.RemoteDisplay; import android.net.NetworkInfo; import android.net.Uri; @@ -75,12 +74,19 @@ final class WifiDisplayController implements DumpUtils.Dump { private static final int DEFAULT_CONTROL_PORT = 7236; private static final int MAX_THROUGHPUT = 50; - private static final int CONNECTION_TIMEOUT_SECONDS = 60; - private static final int RTSP_TIMEOUT_SECONDS = 15; + private static final int CONNECTION_TIMEOUT_SECONDS = 30; + private static final int RTSP_TIMEOUT_SECONDS = 30; private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120; - private static final int DISCOVER_PEERS_MAX_RETRIES = 10; - private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500; + // We repeatedly issue calls to discover peers every so often for a few reasons. + // 1. The initial request may fail and need to retried. + // 2. Discovery will self-abort after any group is initiated, which may not necessarily + // be what we want to have happen. + // 3. Discovery will self-timeout after 2 minutes, whereas we want discovery to + // be occur for as long as a client is requesting it be. + // 4. We don't seem to get updated results for displays we've already found until + // we ask to discover again, particularly for the isSessionAvailable() property. + private static final int DISCOVER_PEERS_INTERVAL_MILLIS = 10000; private static final int CONNECT_MAX_RETRIES = 3; private static final int CONNECT_RETRY_DELAY_MILLIS = 500; @@ -103,12 +109,12 @@ final class WifiDisplayController implements DumpUtils.Dump { // True if Wifi display is enabled by the user. private boolean mWifiDisplayOnSetting; + // True if a scan was requested independent of whether one is actually in progress. + private boolean mScanRequested; + // True if there is a call to discoverPeers in progress. private boolean mDiscoverPeersInProgress; - // Number of discover peers retries remaining. - private int mDiscoverPeersRetriesLeft; - // The device to which we want to connect, or null if we want to be disconnected. private WifiP2pDevice mDesiredDevice; @@ -209,8 +215,8 @@ final class WifiDisplayController implements DumpUtils.Dump { pw.println("mWfdEnabled=" + mWfdEnabled); pw.println("mWfdEnabling=" + mWfdEnabling); pw.println("mNetworkInfo=" + mNetworkInfo); + pw.println("mScanRequested=" + mScanRequested); pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress); - pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft); pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice)); pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice)); pw.println("mDisconnectingDisplay=" + describeWifiP2pDevice(mDisconnectingDevice)); @@ -232,8 +238,18 @@ final class WifiDisplayController implements DumpUtils.Dump { } } - public void requestScan() { - discoverPeers(); + public void requestStartScan() { + if (!mScanRequested) { + mScanRequested = true; + updateScanState(); + } + } + + public void requestStopScan() { + if (mScanRequested) { + mScanRequested = false; + updateScanState(); + } } public void requestConnect(String address) { @@ -282,6 +298,7 @@ final class WifiDisplayController implements DumpUtils.Dump { mWfdEnabling = false; mWfdEnabled = true; reportFeatureState(); + updateScanState(); } } @@ -318,6 +335,7 @@ final class WifiDisplayController implements DumpUtils.Dump { mWfdEnabling = false; mWfdEnabled = false; reportFeatureState(); + updateScanState(); disconnect(); } } @@ -340,12 +358,29 @@ final class WifiDisplayController implements DumpUtils.Dump { WifiDisplayStatus.FEATURE_STATE_OFF; } - private void discoverPeers() { - if (!mDiscoverPeersInProgress) { - mDiscoverPeersInProgress = true; - mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; - handleScanStarted(); - tryDiscoverPeers(); + private void updateScanState() { + if (mScanRequested && mWfdEnabled && mDesiredDevice == null) { + if (!mDiscoverPeersInProgress) { + Slog.i(TAG, "Starting Wifi display scan."); + mDiscoverPeersInProgress = true; + handleScanStarted(); + tryDiscoverPeers(); + } + } else { + if (mDiscoverPeersInProgress) { + // Cancel automatic retry right away. + mHandler.removeCallbacks(mDiscoverPeers); + + // Defer actually stopping discovery if we have a connection attempt in progress. + // The wifi display connection attempt often fails if we are not in discovery + // mode. So we allow discovery to continue until we give up trying to connect. + if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) { + Slog.i(TAG, "Stopping Wifi display scan."); + mDiscoverPeersInProgress = false; + stopPeerDiscovery(); + handleScanFinished(); + } + } } } @@ -357,8 +392,9 @@ final class WifiDisplayController implements DumpUtils.Dump { Slog.d(TAG, "Discover peers succeeded. Requesting peers now."); } - mDiscoverPeersInProgress = false; - requestPeers(); + if (mDiscoverPeersInProgress) { + requestPeers(); + } } @Override @@ -367,30 +403,28 @@ final class WifiDisplayController implements DumpUtils.Dump { Slog.d(TAG, "Discover peers failed with reason " + reason + "."); } - if (mDiscoverPeersInProgress) { - if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (mDiscoverPeersInProgress) { - if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { - mDiscoverPeersRetriesLeft -= 1; - if (DEBUG) { - Slog.d(TAG, "Retrying discovery. Retries left: " - + mDiscoverPeersRetriesLeft); - } - tryDiscoverPeers(); - } else { - handleScanFinished(); - mDiscoverPeersInProgress = false; - } - } - } - }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); - } else { - handleScanFinished(); - mDiscoverPeersInProgress = false; - } + // Ignore the error. + // We will retry automatically in a little bit. + } + }); + + // Retry discover peers periodically until stopped. + mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS); + } + + private void stopPeerDiscovery() { + mWifiP2pManager.stopPeerDiscovery(mWifiP2pChannel, new ActionListener() { + @Override + public void onSuccess() { + if (DEBUG) { + Slog.d(TAG, "Stop peer discovery succeeded."); + } + } + + @Override + public void onFailure(int reason) { + if (DEBUG) { + Slog.d(TAG, "Stop peer discovery failed with reason " + reason + "."); } } }); @@ -415,7 +449,9 @@ final class WifiDisplayController implements DumpUtils.Dump { } } - handleScanFinished(); + if (mDiscoverPeersInProgress) { + handleScanResults(); + } } }); } @@ -429,7 +465,7 @@ final class WifiDisplayController implements DumpUtils.Dump { }); } - private void handleScanFinished() { + private void handleScanResults() { final int count = mAvailableWifiDisplayPeers.size(); final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count); for (int i = 0; i < count; i++) { @@ -441,7 +477,16 @@ final class WifiDisplayController implements DumpUtils.Dump { mHandler.post(new Runnable() { @Override public void run() { - mListener.onScanFinished(displays); + mListener.onScanResults(displays); + } + }); + } + + private void handleScanFinished() { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onScanFinished(); } }); } @@ -484,6 +529,12 @@ final class WifiDisplayController implements DumpUtils.Dump { return; } + if (!mWfdEnabled) { + Slog.i(TAG, "Ignoring request to connect to Wifi display because the " + +" feature is currently disabled: " + device.deviceName); + return; + } + mDesiredDevice = device; mConnectionRetriesLeft = CONNECT_MAX_RETRIES; updateConnection(); @@ -508,6 +559,10 @@ final class WifiDisplayController implements DumpUtils.Dump { * connection is established (or not). */ private void updateConnection() { + // Step 0. Stop scans if necessary to prevent interference while connected. + // Resume scans later when no longer attempting to connect. + updateScanState(); + // Step 1. Before we try to connect to a new device, tell the system we // have disconnected from the old one. if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) { @@ -661,7 +716,7 @@ final class WifiDisplayController implements DumpUtils.Dump { return; // wait for asynchronous callback } - // Step 6. Listen for incoming connections. + // Step 6. Listen for incoming RTSP connection. if (mConnectedDevice != null && mRemoteDisplay == null) { Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo); if (addr == null) { @@ -817,7 +872,11 @@ final class WifiDisplayController implements DumpUtils.Dump { } } else { mConnectedDeviceGroupInfo = null; - disconnect(); + + // Disconnect if we lost the network while connecting or connected to a display. + if (mConnectingDevice != null || mConnectedDevice != null) { + disconnect(); + } // After disconnection for a group, for some reason we have a tendency // to get a peer change notification with an empty list of peers. @@ -828,6 +887,13 @@ final class WifiDisplayController implements DumpUtils.Dump { } } + private final Runnable mDiscoverPeers = new Runnable() { + @Override + public void run() { + tryDiscoverPeers(); + } + }; + private final Runnable mConnectionTimeout = new Runnable() { @Override public void run() { @@ -1033,7 +1099,8 @@ final class WifiDisplayController implements DumpUtils.Dump { void onFeatureStateChanged(int featureState); void onScanStarted(); - void onScanFinished(WifiDisplay[] availableDisplays); + void onScanResults(WifiDisplay[] availableDisplays); + void onScanFinished(); void onDisplayConnecting(WifiDisplay display); void onDisplayConnectionFailed(); diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java index d749e6c626a4..31458056c7f5 100644 --- a/services/java/com/android/server/input/InputManagerService.java +++ b/services/java/com/android/server/input/InputManagerService.java @@ -294,6 +294,7 @@ public class InputManagerService extends IInputManager.Stub IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addDataScheme("package"); mContext.registerReceiver(new BroadcastReceiver() { @Override diff --git a/services/java/com/android/server/media/MediaRouterService.java b/services/java/com/android/server/media/MediaRouterService.java new file mode 100644 index 000000000000..a31695b885b2 --- /dev/null +++ b/services/java/com/android/server/media/MediaRouterService.java @@ -0,0 +1,1423 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import com.android.internal.util.Objects; +import com.android.server.Watchdog; + +import android.Manifest; +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.media.AudioSystem; +import android.media.IMediaRouterClient; +import android.media.IMediaRouterService; +import android.media.MediaRouter; +import android.media.MediaRouterClientState; +import android.media.RemoteDisplayState; +import android.media.RemoteDisplayState.RemoteDisplayInfo; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.TimeUtils; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Provides a mechanism for discovering media routes and manages media playback + * behalf of applications. + * <p> + * Currently supports discovering remote displays via remote display provider + * services that have been registered by applications. + * </p> + */ +public final class MediaRouterService extends IMediaRouterService.Stub + implements Watchdog.Monitor { + private static final String TAG = "MediaRouterService"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + /** + * Timeout in milliseconds for a selected route to transition from a + * disconnected state to a connecting state. If we don't observe any + * progress within this interval, then we will give up and unselect the route. + */ + static final long CONNECTING_TIMEOUT = 5000; + + /** + * Timeout in milliseconds for a selected route to transition from a + * connecting state to a connected state. If we don't observe any + * progress within this interval, then we will give up and unselect the route. + */ + static final long CONNECTED_TIMEOUT = 60000; + + private final Context mContext; + + // State guarded by mLock. + private final Object mLock = new Object(); + private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>(); + private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = + new ArrayMap<IBinder, ClientRecord>(); + private int mCurrentUserId = -1; + + public MediaRouterService(Context context) { + mContext = context; + Watchdog.getInstance().addMonitor(this); + } + + public void systemRunning() { + IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) { + switchUser(); + } + } + }, filter); + + switchUser(); + } + + @Override + public void monitor() { + synchronized (mLock) { /* check for deadlock */ } + } + + // Binder call + @Override + public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final int uid = Binder.getCallingUid(); + if (!validatePackageName(uid, packageName)) { + throw new SecurityException("packageName must match the calling uid"); + } + + final int pid = Binder.getCallingPid(); + final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, + false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName); + final boolean trusted = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) == + PackageManager.PERMISSION_GRANTED; + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + registerClientLocked(client, pid, packageName, resolvedUserId, trusted); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void unregisterClient(IMediaRouterClient client) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + unregisterClientLocked(client, false); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public MediaRouterClientState getState(IMediaRouterClient client) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + return getStateLocked(client); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void setDiscoveryRequest(IMediaRouterClient client, + int routeTypes, boolean activeScan) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setDiscoveryRequestLocked(client, routeTypes, activeScan); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + // A null routeId means that the client wants to unselect its current route. + // The explicit flag indicates whether the change was explicitly requested by the + // user or the application which may cause changes to propagate out to the rest + // of the system. Should be false when the change is in response to a new globally + // selected route or a default selection. + @Override + public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setSelectedRouteLocked(client, routeId, explicit); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + requestSetVolumeLocked(client, routeId, volume); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + requestUpdateVolumeLocked(client, routeId, direction); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump MediaRouterService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)"); + pw.println(); + pw.println("Global state"); + pw.println(" mCurrentUserId=" + mCurrentUserId); + + synchronized (mLock) { + final int count = mUserRecords.size(); + for (int i = 0; i < count; i++) { + UserRecord userRecord = mUserRecords.valueAt(i); + pw.println(); + userRecord.dump(pw, ""); + } + } + } + + void switchUser() { + synchronized (mLock) { + int userId = ActivityManager.getCurrentUser(); + if (mCurrentUserId != userId) { + final int oldUserId = mCurrentUserId; + mCurrentUserId = userId; // do this first + + UserRecord oldUser = mUserRecords.get(oldUserId); + if (oldUser != null) { + oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP); + disposeUserIfNeededLocked(oldUser); // since no longer current user + } + + UserRecord newUser = mUserRecords.get(userId); + if (newUser != null) { + newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START); + } + } + } + } + + void clientDied(ClientRecord clientRecord) { + synchronized (mLock) { + unregisterClientLocked(clientRecord.mClient, true); + } + } + + private void registerClientLocked(IMediaRouterClient client, + int pid, String packageName, int userId, boolean trusted) { + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord == null) { + boolean newUser = false; + UserRecord userRecord = mUserRecords.get(userId); + if (userRecord == null) { + userRecord = new UserRecord(userId); + newUser = true; + } + clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted); + try { + binder.linkToDeath(clientRecord, 0); + } catch (RemoteException ex) { + throw new RuntimeException("Media router client died prematurely.", ex); + } + + if (newUser) { + mUserRecords.put(userId, userRecord); + initializeUserLocked(userRecord); + } + + userRecord.mClientRecords.add(clientRecord); + mAllClientRecords.put(binder, clientRecord); + initializeClientLocked(clientRecord); + } + } + + private void unregisterClientLocked(IMediaRouterClient client, boolean died) { + ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); + if (clientRecord != null) { + UserRecord userRecord = clientRecord.mUserRecord; + userRecord.mClientRecords.remove(clientRecord); + disposeClientLocked(clientRecord, died); + disposeUserIfNeededLocked(userRecord); // since client removed from user + } + } + + private MediaRouterClientState getStateLocked(IMediaRouterClient client) { + ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); + if (clientRecord != null) { + return clientRecord.getState(); + } + return null; + } + + private void setDiscoveryRequestLocked(IMediaRouterClient client, + int routeTypes, boolean activeScan) { + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord != null) { + // Only let the system discover remote display routes for now. + if (!clientRecord.mTrusted) { + routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; + } + + if (clientRecord.mRouteTypes != routeTypes + || clientRecord.mActiveScan != activeScan) { + if (DEBUG) { + Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x" + + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan); + } + clientRecord.mRouteTypes = routeTypes; + clientRecord.mActiveScan = activeScan; + clientRecord.mUserRecord.mHandler.sendEmptyMessage( + UserHandler.MSG_UPDATE_DISCOVERY_REQUEST); + } + } + } + + private void setSelectedRouteLocked(IMediaRouterClient client, + String routeId, boolean explicit) { + ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); + if (clientRecord != null) { + final String oldRouteId = clientRecord.mSelectedRouteId; + if (!Objects.equal(routeId, oldRouteId)) { + if (DEBUG) { + Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId + + ", oldRouteId=" + oldRouteId + + ", explicit=" + explicit); + } + + clientRecord.mSelectedRouteId = routeId; + if (explicit) { + // Any app can disconnect from the globally selected route. + if (oldRouteId != null) { + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget(); + } + // Only let the system connect to new global routes for now. + // A similar check exists in the display manager for wifi display. + if (routeId != null && clientRecord.mTrusted) { + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget(); + } + } + } + } + } + + private void requestSetVolumeLocked(IMediaRouterClient client, + String routeId, int volume) { + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord != null) { + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget(); + } + } + + private void requestUpdateVolumeLocked(IMediaRouterClient client, + String routeId, int direction) { + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord != null) { + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget(); + } + } + + private void initializeUserLocked(UserRecord userRecord) { + if (DEBUG) { + Slog.d(TAG, userRecord + ": Initialized"); + } + if (userRecord.mUserId == mCurrentUserId) { + userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START); + } + } + + private void disposeUserIfNeededLocked(UserRecord userRecord) { + // If there are no records left and the user is no longer current then go ahead + // and purge the user record and all of its associated state. If the user is current + // then leave it alone since we might be connected to a route or want to query + // the same route information again soon. + if (userRecord.mUserId != mCurrentUserId + && userRecord.mClientRecords.isEmpty()) { + if (DEBUG) { + Slog.d(TAG, userRecord + ": Disposed"); + } + mUserRecords.remove(userRecord.mUserId); + // Note: User already stopped (by switchUser) so no need to send stop message here. + } + } + + private void initializeClientLocked(ClientRecord clientRecord) { + if (DEBUG) { + Slog.d(TAG, clientRecord + ": Registered"); + } + } + + private void disposeClientLocked(ClientRecord clientRecord, boolean died) { + if (DEBUG) { + if (died) { + Slog.d(TAG, clientRecord + ": Died!"); + } else { + Slog.d(TAG, clientRecord + ": Unregistered"); + } + } + if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) { + clientRecord.mUserRecord.mHandler.sendEmptyMessage( + UserHandler.MSG_UPDATE_DISCOVERY_REQUEST); + } + clientRecord.dispose(); + } + + private boolean validatePackageName(int uid, String packageName) { + if (packageName != null) { + String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); + if (packageNames != null) { + for (String n : packageNames) { + if (n.equals(packageName)) { + return true; + } + } + } + } + return false; + } + + /** + * Information about a particular client of the media router. + * The contents of this object is guarded by mLock. + */ + final class ClientRecord implements DeathRecipient { + public final UserRecord mUserRecord; + public final IMediaRouterClient mClient; + public final int mPid; + public final String mPackageName; + public final boolean mTrusted; + + public int mRouteTypes; + public boolean mActiveScan; + public String mSelectedRouteId; + + public ClientRecord(UserRecord userRecord, IMediaRouterClient client, + int pid, String packageName, boolean trusted) { + mUserRecord = userRecord; + mClient = client; + mPid = pid; + mPackageName = packageName; + mTrusted = trusted; + } + + public void dispose() { + mClient.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + clientDied(this); + } + + MediaRouterClientState getState() { + return mTrusted ? mUserRecord.mTrustedState : mUserRecord.mUntrustedState; + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + this); + + final String indent = prefix + " "; + pw.println(indent + "mTrusted=" + mTrusted); + pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes)); + pw.println(indent + "mActiveScan=" + mActiveScan); + pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId); + } + + @Override + public String toString() { + return "Client " + mPackageName + " (pid " + mPid + ")"; + } + } + + /** + * Information about a particular user. + * The contents of this object is guarded by mLock. + */ + final class UserRecord { + public final int mUserId; + public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>(); + public final UserHandler mHandler; + public MediaRouterClientState mTrustedState; + public MediaRouterClientState mUntrustedState; + + public UserRecord(int userId) { + mUserId = userId; + mHandler = new UserHandler(MediaRouterService.this, this); + } + + public void dump(final PrintWriter pw, String prefix) { + pw.println(prefix + this); + + final String indent = prefix + " "; + final int clientCount = mClientRecords.size(); + if (clientCount != 0) { + for (int i = 0; i < clientCount; i++) { + mClientRecords.get(i).dump(pw, indent); + } + } else { + pw.println(indent + "<no clients>"); + } + + pw.println(indent + "State"); + pw.println(indent + "mTrustedState=" + mTrustedState); + pw.println(indent + "mUntrustedState=" + mUntrustedState); + + if (!mHandler.runWithScissors(new Runnable() { + @Override + public void run() { + mHandler.dump(pw, indent); + } + }, 1000)) { + pw.println(indent + "<could not dump handler state>"); + } + } + + @Override + public String toString() { + return "User " + mUserId; + } + } + + /** + * Media router handler + * <p> + * Since remote display providers are designed to be single-threaded by nature, + * this class encapsulates all of the associated functionality and exports state + * to the service as it evolves. + * </p><p> + * One important task of this class is to keep track of the current globally selected + * route id for certain routes that have global effects, such as remote displays. + * Global route selections override local selections made within apps. The change + * is propagated to all apps so that they are all in sync. Synchronization works + * both ways. Whenever the globally selected route is explicitly unselected by any + * app, then it becomes unselected globally and all apps are informed. + * </p><p> + * This class is currently hardcoded to work with remote display providers but + * it is intended to be eventually extended to support more general route providers + * similar to the support library media router. + * </p> + */ + static final class UserHandler extends Handler + implements RemoteDisplayProviderWatcher.Callback, + RemoteDisplayProviderProxy.Callback { + public static final int MSG_START = 1; + public static final int MSG_STOP = 2; + public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3; + public static final int MSG_SELECT_ROUTE = 4; + public static final int MSG_UNSELECT_ROUTE = 5; + public static final int MSG_REQUEST_SET_VOLUME = 6; + public static final int MSG_REQUEST_UPDATE_VOLUME = 7; + private static final int MSG_UPDATE_CLIENT_STATE = 8; + private static final int MSG_CONNECTION_TIMED_OUT = 9; + + private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1; + private static final int TIMEOUT_REASON_CONNECTION_LOST = 2; + private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3; + private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4; + + // The relative order of these constants is important and expresses progress + // through the process of connecting to a route. + private static final int PHASE_NOT_AVAILABLE = -1; + private static final int PHASE_NOT_CONNECTED = 0; + private static final int PHASE_CONNECTING = 1; + private static final int PHASE_CONNECTED = 2; + + private final MediaRouterService mService; + private final UserRecord mUserRecord; + private final RemoteDisplayProviderWatcher mWatcher; + private final ArrayList<ProviderRecord> mProviderRecords = + new ArrayList<ProviderRecord>(); + private final ArrayList<IMediaRouterClient> mTempClients = + new ArrayList<IMediaRouterClient>(); + + private boolean mRunning; + private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE; + private RouteRecord mGloballySelectedRouteRecord; + private int mConnectionPhase = PHASE_NOT_AVAILABLE; + private int mConnectionTimeoutReason; + private long mConnectionTimeoutStartTime; + private boolean mClientStateUpdateScheduled; + + public UserHandler(MediaRouterService service, UserRecord userRecord) { + super(Looper.getMainLooper(), null, true); + mService = service; + mUserRecord = userRecord; + mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this, + this, mUserRecord.mUserId); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START: { + start(); + break; + } + case MSG_STOP: { + stop(); + break; + } + case MSG_UPDATE_DISCOVERY_REQUEST: { + updateDiscoveryRequest(); + break; + } + case MSG_SELECT_ROUTE: { + selectRoute((String)msg.obj); + break; + } + case MSG_UNSELECT_ROUTE: { + unselectRoute((String)msg.obj); + break; + } + case MSG_REQUEST_SET_VOLUME: { + requestSetVolume((String)msg.obj, msg.arg1); + break; + } + case MSG_REQUEST_UPDATE_VOLUME: { + requestUpdateVolume((String)msg.obj, msg.arg1); + break; + } + case MSG_UPDATE_CLIENT_STATE: { + updateClientState(); + break; + } + case MSG_CONNECTION_TIMED_OUT: { + connectionTimedOut(); + break; + } + } + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "Handler"); + + final String indent = prefix + " "; + pw.println(indent + "mRunning=" + mRunning); + pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode); + pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord); + pw.println(indent + "mConnectionPhase=" + mConnectionPhase); + pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason); + pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ? + TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>")); + + mWatcher.dump(pw, prefix); + + final int providerCount = mProviderRecords.size(); + if (providerCount != 0) { + for (int i = 0; i < providerCount; i++) { + mProviderRecords.get(i).dump(pw, prefix); + } + } else { + pw.println(indent + "<no providers>"); + } + } + + private void start() { + if (!mRunning) { + mRunning = true; + mWatcher.start(); // also starts all providers + } + } + + private void stop() { + if (mRunning) { + mRunning = false; + unselectGloballySelectedRoute(); + mWatcher.stop(); // also stops all providers + } + } + + private void updateDiscoveryRequest() { + int routeTypes = 0; + boolean activeScan = false; + synchronized (mService.mLock) { + final int count = mUserRecord.mClientRecords.size(); + for (int i = 0; i < count; i++) { + ClientRecord clientRecord = mUserRecord.mClientRecords.get(i); + routeTypes |= clientRecord.mRouteTypes; + activeScan |= clientRecord.mActiveScan; + } + } + + final int newDiscoveryMode; + if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) { + if (activeScan) { + newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE; + } else { + newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE; + } + } else { + newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE; + } + + if (mDiscoveryMode != newDiscoveryMode) { + mDiscoveryMode = newDiscoveryMode; + final int count = mProviderRecords.size(); + for (int i = 0; i < count; i++) { + mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode); + } + } + } + + private void selectRoute(String routeId) { + if (routeId != null + && (mGloballySelectedRouteRecord == null + || !routeId.equals(mGloballySelectedRouteRecord.getUniqueId()))) { + RouteRecord routeRecord = findRouteRecord(routeId); + if (routeRecord != null) { + unselectGloballySelectedRoute(); + + Slog.i(TAG, "Selected global route:" + routeRecord); + mGloballySelectedRouteRecord = routeRecord; + checkGloballySelectedRouteState(); + routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId()); + + scheduleUpdateClientState(); + } + } + } + + private void unselectRoute(String routeId) { + if (routeId != null + && mGloballySelectedRouteRecord != null + && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) { + unselectGloballySelectedRoute(); + } + } + + private void unselectGloballySelectedRoute() { + if (mGloballySelectedRouteRecord != null) { + Slog.i(TAG, "Unselected global route:" + mGloballySelectedRouteRecord); + mGloballySelectedRouteRecord.getProvider().setSelectedDisplay(null); + mGloballySelectedRouteRecord = null; + checkGloballySelectedRouteState(); + + scheduleUpdateClientState(); + } + } + + private void requestSetVolume(String routeId, int volume) { + if (mGloballySelectedRouteRecord != null + && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) { + mGloballySelectedRouteRecord.getProvider().setDisplayVolume(volume); + } + } + + private void requestUpdateVolume(String routeId, int direction) { + if (mGloballySelectedRouteRecord != null + && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) { + mGloballySelectedRouteRecord.getProvider().adjustDisplayVolume(direction); + } + } + + @Override + public void addProvider(RemoteDisplayProviderProxy provider) { + provider.setCallback(this); + provider.setDiscoveryMode(mDiscoveryMode); + provider.setSelectedDisplay(null); // just to be safe + + ProviderRecord providerRecord = new ProviderRecord(provider); + mProviderRecords.add(providerRecord); + providerRecord.updateDescriptor(provider.getDisplayState()); + + scheduleUpdateClientState(); + } + + @Override + public void removeProvider(RemoteDisplayProviderProxy provider) { + int index = findProviderRecord(provider); + if (index >= 0) { + ProviderRecord providerRecord = mProviderRecords.remove(index); + providerRecord.updateDescriptor(null); // mark routes invalid + provider.setCallback(null); + provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE); + + checkGloballySelectedRouteState(); + scheduleUpdateClientState(); + } + } + + @Override + public void onDisplayStateChanged(RemoteDisplayProviderProxy provider, + RemoteDisplayState state) { + updateProvider(provider, state); + } + + private void updateProvider(RemoteDisplayProviderProxy provider, + RemoteDisplayState state) { + int index = findProviderRecord(provider); + if (index >= 0) { + ProviderRecord providerRecord = mProviderRecords.get(index); + if (providerRecord.updateDescriptor(state)) { + checkGloballySelectedRouteState(); + scheduleUpdateClientState(); + } + } + } + + /** + * This function is called whenever the state of the globally selected route + * may have changed. It checks the state and updates timeouts or unselects + * the route as appropriate. + */ + private void checkGloballySelectedRouteState() { + // Unschedule timeouts when the route is unselected. + if (mGloballySelectedRouteRecord == null) { + mConnectionPhase = PHASE_NOT_AVAILABLE; + updateConnectionTimeout(0); + return; + } + + // Ensure that the route is still present and enabled. + if (!mGloballySelectedRouteRecord.isValid() + || !mGloballySelectedRouteRecord.isEnabled()) { + updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE); + return; + } + + // Make sure we haven't lost our connection. + final int oldPhase = mConnectionPhase; + mConnectionPhase = getConnectionPhase(mGloballySelectedRouteRecord.getStatus()); + if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) { + updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST); + return; + } + + // Check the route status. + switch (mConnectionPhase) { + case PHASE_CONNECTED: + if (oldPhase != PHASE_CONNECTED) { + Slog.i(TAG, "Connected to global route: " + + mGloballySelectedRouteRecord); + } + updateConnectionTimeout(0); + break; + case PHASE_CONNECTING: + if (oldPhase != PHASE_CONNECTING) { + Slog.i(TAG, "Connecting to global route: " + + mGloballySelectedRouteRecord); + } + updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED); + break; + case PHASE_NOT_CONNECTED: + updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING); + break; + case PHASE_NOT_AVAILABLE: + default: + updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE); + break; + } + } + + private void updateConnectionTimeout(int reason) { + if (reason != mConnectionTimeoutReason) { + if (mConnectionTimeoutReason != 0) { + removeMessages(MSG_CONNECTION_TIMED_OUT); + } + mConnectionTimeoutReason = reason; + mConnectionTimeoutStartTime = SystemClock.uptimeMillis(); + switch (reason) { + case TIMEOUT_REASON_NOT_AVAILABLE: + case TIMEOUT_REASON_CONNECTION_LOST: + // Route became unavailable or connection lost. + // Unselect it immediately. + sendEmptyMessage(MSG_CONNECTION_TIMED_OUT); + break; + case TIMEOUT_REASON_WAITING_FOR_CONNECTING: + // Waiting for route to start connecting. + sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT); + break; + case TIMEOUT_REASON_WAITING_FOR_CONNECTED: + // Waiting for route to complete connection. + sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT); + break; + } + } + } + + private void connectionTimedOut() { + if (mConnectionTimeoutReason == 0 || mGloballySelectedRouteRecord == null) { + // Shouldn't get here. There must be a bug somewhere. + Log.wtf(TAG, "Handled connection timeout for no reason."); + return; + } + + switch (mConnectionTimeoutReason) { + case TIMEOUT_REASON_NOT_AVAILABLE: + Slog.i(TAG, "Global route no longer available: " + + mGloballySelectedRouteRecord); + break; + case TIMEOUT_REASON_CONNECTION_LOST: + Slog.i(TAG, "Global route connection lost: " + + mGloballySelectedRouteRecord); + break; + case TIMEOUT_REASON_WAITING_FOR_CONNECTING: + Slog.i(TAG, "Global route timed out while waiting for " + + "connection attempt to begin after " + + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime) + + " ms: " + mGloballySelectedRouteRecord); + break; + case TIMEOUT_REASON_WAITING_FOR_CONNECTED: + Slog.i(TAG, "Global route timed out while connecting after " + + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime) + + " ms: " + mGloballySelectedRouteRecord); + break; + } + mConnectionTimeoutReason = 0; + + unselectGloballySelectedRoute(); + } + + private void scheduleUpdateClientState() { + if (!mClientStateUpdateScheduled) { + mClientStateUpdateScheduled = true; + sendEmptyMessage(MSG_UPDATE_CLIENT_STATE); + } + } + + private void updateClientState() { + mClientStateUpdateScheduled = false; + + final String globallySelectedRouteId = mGloballySelectedRouteRecord != null ? + mGloballySelectedRouteRecord.getUniqueId() : null; + + // Build a new client state for trusted clients. + MediaRouterClientState trustedState = new MediaRouterClientState(); + trustedState.globallySelectedRouteId = globallySelectedRouteId; + final int providerCount = mProviderRecords.size(); + for (int i = 0; i < providerCount; i++) { + mProviderRecords.get(i).appendClientState(trustedState); + } + + // Build a new client state for untrusted clients that can only see + // the currently selected route. + MediaRouterClientState untrustedState = new MediaRouterClientState(); + untrustedState.globallySelectedRouteId = globallySelectedRouteId; + if (globallySelectedRouteId != null) { + untrustedState.routes.add(trustedState.getRoute(globallySelectedRouteId)); + } + + try { + synchronized (mService.mLock) { + // Update the UserRecord. + mUserRecord.mTrustedState = trustedState; + mUserRecord.mUntrustedState = untrustedState; + + // Collect all clients. + final int count = mUserRecord.mClientRecords.size(); + for (int i = 0; i < count; i++) { + mTempClients.add(mUserRecord.mClientRecords.get(i).mClient); + } + } + + // Notify all clients (outside of the lock). + final int count = mTempClients.size(); + for (int i = 0; i < count; i++) { + try { + mTempClients.get(i).onStateChanged(); + } catch (RemoteException ex) { + // ignore errors, client probably died + } + } + } finally { + // Clear the list in preparation for the next time. + mTempClients.clear(); + } + } + + private int findProviderRecord(RemoteDisplayProviderProxy provider) { + final int count = mProviderRecords.size(); + for (int i = 0; i < count; i++) { + ProviderRecord record = mProviderRecords.get(i); + if (record.getProvider() == provider) { + return i; + } + } + return -1; + } + + private RouteRecord findRouteRecord(String uniqueId) { + final int count = mProviderRecords.size(); + for (int i = 0; i < count; i++) { + RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId); + if (record != null) { + return record; + } + } + return null; + } + + private static int getConnectionPhase(int status) { + switch (status) { + case MediaRouter.RouteInfo.STATUS_NONE: + case MediaRouter.RouteInfo.STATUS_CONNECTED: + return PHASE_CONNECTED; + case MediaRouter.RouteInfo.STATUS_CONNECTING: + return PHASE_CONNECTING; + case MediaRouter.RouteInfo.STATUS_SCANNING: + case MediaRouter.RouteInfo.STATUS_AVAILABLE: + return PHASE_NOT_CONNECTED; + case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE: + case MediaRouter.RouteInfo.STATUS_IN_USE: + default: + return PHASE_NOT_AVAILABLE; + } + } + + static final class ProviderRecord { + private final RemoteDisplayProviderProxy mProvider; + private final String mUniquePrefix; + private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>(); + private RemoteDisplayState mDescriptor; + + public ProviderRecord(RemoteDisplayProviderProxy provider) { + mProvider = provider; + mUniquePrefix = provider.getFlattenedComponentName() + ":"; + } + + public RemoteDisplayProviderProxy getProvider() { + return mProvider; + } + + public String getUniquePrefix() { + return mUniquePrefix; + } + + public boolean updateDescriptor(RemoteDisplayState descriptor) { + boolean changed = false; + if (mDescriptor != descriptor) { + mDescriptor = descriptor; + + // Update all existing routes and reorder them to match + // the order of their descriptors. + int targetIndex = 0; + if (descriptor != null) { + if (descriptor.isValid()) { + final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays; + final int routeCount = routeDescriptors.size(); + for (int i = 0; i < routeCount; i++) { + final RemoteDisplayInfo routeDescriptor = + routeDescriptors.get(i); + final String descriptorId = routeDescriptor.id; + final int sourceIndex = findRouteByDescriptorId(descriptorId); + if (sourceIndex < 0) { + // Add the route to the provider. + String uniqueId = assignRouteUniqueId(descriptorId); + RouteRecord route = + new RouteRecord(this, descriptorId, uniqueId); + mRoutes.add(targetIndex++, route); + route.updateDescriptor(routeDescriptor); + changed = true; + } else if (sourceIndex < targetIndex) { + // Ignore route with duplicate id. + Slog.w(TAG, "Ignoring route descriptor with duplicate id: " + + routeDescriptor); + } else { + // Reorder existing route within the list. + RouteRecord route = mRoutes.get(sourceIndex); + Collections.swap(mRoutes, sourceIndex, targetIndex++); + changed |= route.updateDescriptor(routeDescriptor); + } + } + } else { + Slog.w(TAG, "Ignoring invalid descriptor from media route provider: " + + mProvider.getFlattenedComponentName()); + } + } + + // Dispose all remaining routes that do not have matching descriptors. + for (int i = mRoutes.size() - 1; i >= targetIndex; i--) { + RouteRecord route = mRoutes.remove(i); + route.updateDescriptor(null); // mark route invalid + changed = true; + } + } + return changed; + } + + public void appendClientState(MediaRouterClientState state) { + final int routeCount = mRoutes.size(); + for (int i = 0; i < routeCount; i++) { + state.routes.add(mRoutes.get(i).getInfo()); + } + } + + public RouteRecord findRouteByUniqueId(String uniqueId) { + final int routeCount = mRoutes.size(); + for (int i = 0; i < routeCount; i++) { + RouteRecord route = mRoutes.get(i); + if (route.getUniqueId().equals(uniqueId)) { + return route; + } + } + return null; + } + + private int findRouteByDescriptorId(String descriptorId) { + final int routeCount = mRoutes.size(); + for (int i = 0; i < routeCount; i++) { + RouteRecord route = mRoutes.get(i); + if (route.getDescriptorId().equals(descriptorId)) { + return i; + } + } + return -1; + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + this); + + final String indent = prefix + " "; + mProvider.dump(pw, indent); + + final int routeCount = mRoutes.size(); + if (routeCount != 0) { + for (int i = 0; i < routeCount; i++) { + mRoutes.get(i).dump(pw, indent); + } + } else { + pw.println(indent + "<no routes>"); + } + } + + @Override + public String toString() { + return "Provider " + mProvider.getFlattenedComponentName(); + } + + private String assignRouteUniqueId(String descriptorId) { + return mUniquePrefix + descriptorId; + } + } + + static final class RouteRecord { + private final ProviderRecord mProviderRecord; + private final String mDescriptorId; + private final MediaRouterClientState.RouteInfo mMutableInfo; + private MediaRouterClientState.RouteInfo mImmutableInfo; + private RemoteDisplayInfo mDescriptor; + + public RouteRecord(ProviderRecord providerRecord, + String descriptorId, String uniqueId) { + mProviderRecord = providerRecord; + mDescriptorId = descriptorId; + mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId); + } + + public RemoteDisplayProviderProxy getProvider() { + return mProviderRecord.getProvider(); + } + + public ProviderRecord getProviderRecord() { + return mProviderRecord; + } + + public String getDescriptorId() { + return mDescriptorId; + } + + public String getUniqueId() { + return mMutableInfo.id; + } + + public MediaRouterClientState.RouteInfo getInfo() { + if (mImmutableInfo == null) { + mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo); + } + return mImmutableInfo; + } + + public boolean isValid() { + return mDescriptor != null; + } + + public boolean isEnabled() { + return mMutableInfo.enabled; + } + + public int getStatus() { + return mMutableInfo.statusCode; + } + + public boolean updateDescriptor(RemoteDisplayInfo descriptor) { + boolean changed = false; + if (mDescriptor != descriptor) { + mDescriptor = descriptor; + if (descriptor != null) { + final String name = computeName(descriptor); + if (!Objects.equal(mMutableInfo.name, name)) { + mMutableInfo.name = name; + changed = true; + } + final String description = computeDescription(descriptor); + if (!Objects.equal(mMutableInfo.description, description)) { + mMutableInfo.description = description; + changed = true; + } + final int supportedTypes = computeSupportedTypes(descriptor); + if (mMutableInfo.supportedTypes != supportedTypes) { + mMutableInfo.supportedTypes = supportedTypes; + changed = true; + } + final boolean enabled = computeEnabled(descriptor); + if (mMutableInfo.enabled != enabled) { + mMutableInfo.enabled = enabled; + changed = true; + } + final int statusCode = computeStatusCode(descriptor); + if (mMutableInfo.statusCode != statusCode) { + mMutableInfo.statusCode = statusCode; + changed = true; + } + final int playbackType = computePlaybackType(descriptor); + if (mMutableInfo.playbackType != playbackType) { + mMutableInfo.playbackType = playbackType; + changed = true; + } + final int playbackStream = computePlaybackStream(descriptor); + if (mMutableInfo.playbackStream != playbackStream) { + mMutableInfo.playbackStream = playbackStream; + changed = true; + } + final int volume = computeVolume(descriptor); + if (mMutableInfo.volume != volume) { + mMutableInfo.volume = volume; + changed = true; + } + final int volumeMax = computeVolumeMax(descriptor); + if (mMutableInfo.volumeMax != volumeMax) { + mMutableInfo.volumeMax = volumeMax; + changed = true; + } + final int volumeHandling = computeVolumeHandling(descriptor); + if (mMutableInfo.volumeHandling != volumeHandling) { + mMutableInfo.volumeHandling = volumeHandling; + changed = true; + } + final int presentationDisplayId = computePresentationDisplayId(descriptor); + if (mMutableInfo.presentationDisplayId != presentationDisplayId) { + mMutableInfo.presentationDisplayId = presentationDisplayId; + changed = true; + } + } + } + if (changed) { + mImmutableInfo = null; + } + return changed; + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + this); + + final String indent = prefix + " "; + pw.println(indent + "mMutableInfo=" + mMutableInfo); + pw.println(indent + "mDescriptorId=" + mDescriptorId); + pw.println(indent + "mDescriptor=" + mDescriptor); + } + + @Override + public String toString() { + return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")"; + } + + private static String computeName(RemoteDisplayInfo descriptor) { + // Note that isValid() already ensures the name is non-empty. + return descriptor.name; + } + + private static String computeDescription(RemoteDisplayInfo descriptor) { + final String description = descriptor.description; + return TextUtils.isEmpty(description) ? null : description; + } + + private static int computeSupportedTypes(RemoteDisplayInfo descriptor) { + return MediaRouter.ROUTE_TYPE_LIVE_AUDIO + | MediaRouter.ROUTE_TYPE_LIVE_VIDEO + | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; + } + + private static boolean computeEnabled(RemoteDisplayInfo descriptor) { + switch (descriptor.status) { + case RemoteDisplayInfo.STATUS_CONNECTED: + case RemoteDisplayInfo.STATUS_CONNECTING: + case RemoteDisplayInfo.STATUS_AVAILABLE: + return true; + default: + return false; + } + } + + private static int computeStatusCode(RemoteDisplayInfo descriptor) { + switch (descriptor.status) { + case RemoteDisplayInfo.STATUS_NOT_AVAILABLE: + return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE; + case RemoteDisplayInfo.STATUS_AVAILABLE: + return MediaRouter.RouteInfo.STATUS_AVAILABLE; + case RemoteDisplayInfo.STATUS_IN_USE: + return MediaRouter.RouteInfo.STATUS_IN_USE; + case RemoteDisplayInfo.STATUS_CONNECTING: + return MediaRouter.RouteInfo.STATUS_CONNECTING; + case RemoteDisplayInfo.STATUS_CONNECTED: + return MediaRouter.RouteInfo.STATUS_CONNECTED; + default: + return MediaRouter.RouteInfo.STATUS_NONE; + } + } + + private static int computePlaybackType(RemoteDisplayInfo descriptor) { + return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE; + } + + private static int computePlaybackStream(RemoteDisplayInfo descriptor) { + return AudioSystem.STREAM_MUSIC; + } + + private static int computeVolume(RemoteDisplayInfo descriptor) { + final int volume = descriptor.volume; + final int volumeMax = descriptor.volumeMax; + if (volume < 0) { + return 0; + } else if (volume > volumeMax) { + return volumeMax; + } + return volume; + } + + private static int computeVolumeMax(RemoteDisplayInfo descriptor) { + final int volumeMax = descriptor.volumeMax; + return volumeMax > 0 ? volumeMax : 0; + } + + private static int computeVolumeHandling(RemoteDisplayInfo descriptor) { + final int volumeHandling = descriptor.volumeHandling; + switch (volumeHandling) { + case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE: + return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; + case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED: + default: + return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; + } + } + + private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) { + // The MediaRouter class validates that the id corresponds to an extant + // presentation display. So all we do here is canonicalize the null case. + final int displayId = descriptor.presentationDisplayId; + return displayId < 0 ? -1 : displayId; + } + } + } +} diff --git a/services/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java new file mode 100644 index 000000000000..b248ee0759d8 --- /dev/null +++ b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import com.android.internal.util.Objects; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.media.IRemoteDisplayCallback; +import android.media.IRemoteDisplayProvider; +import android.media.RemoteDisplayState; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.IBinder.DeathRecipient; +import android.os.UserHandle; +import android.util.Log; +import android.util.Slog; + +import java.io.PrintWriter; +import java.lang.ref.WeakReference; + +/** + * Maintains a connection to a particular remote display provider service. + */ +final class RemoteDisplayProviderProxy implements ServiceConnection { + private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Context mContext; + private final ComponentName mComponentName; + private final int mUserId; + private final Handler mHandler; + + private Callback mDisplayStateCallback; + + // Connection state + private boolean mRunning; + private boolean mBound; + private Connection mActiveConnection; + private boolean mConnectionReady; + + // Logical state + private int mDiscoveryMode; + private String mSelectedDisplayId; + private RemoteDisplayState mDisplayState; + private boolean mScheduledDisplayStateChangedCallback; + + public RemoteDisplayProviderProxy(Context context, ComponentName componentName, + int userId) { + mContext = context; + mComponentName = componentName; + mUserId = userId; + mHandler = new Handler(); + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "Proxy"); + pw.println(prefix + " mUserId=" + mUserId); + pw.println(prefix + " mRunning=" + mRunning); + pw.println(prefix + " mBound=" + mBound); + pw.println(prefix + " mActiveConnection=" + mActiveConnection); + pw.println(prefix + " mConnectionReady=" + mConnectionReady); + pw.println(prefix + " mDiscoveryMode=" + mDiscoveryMode); + pw.println(prefix + " mSelectedDisplayId=" + mSelectedDisplayId); + pw.println(prefix + " mDisplayState=" + mDisplayState); + } + + public void setCallback(Callback callback) { + mDisplayStateCallback = callback; + } + + public RemoteDisplayState getDisplayState() { + return mDisplayState; + } + + public void setDiscoveryMode(int mode) { + if (mDiscoveryMode != mode) { + mDiscoveryMode = mode; + if (mConnectionReady) { + mActiveConnection.setDiscoveryMode(mode); + } + updateBinding(); + } + } + + public void setSelectedDisplay(String id) { + if (!Objects.equal(mSelectedDisplayId, id)) { + if (mConnectionReady && mSelectedDisplayId != null) { + mActiveConnection.disconnect(mSelectedDisplayId); + } + mSelectedDisplayId = id; + if (mConnectionReady && id != null) { + mActiveConnection.connect(id); + } + updateBinding(); + } + } + + public void setDisplayVolume(int volume) { + if (mConnectionReady && mSelectedDisplayId != null) { + mActiveConnection.setVolume(mSelectedDisplayId, volume); + } + } + + public void adjustDisplayVolume(int delta) { + if (mConnectionReady && mSelectedDisplayId != null) { + mActiveConnection.adjustVolume(mSelectedDisplayId, delta); + } + } + + public boolean hasComponentName(String packageName, String className) { + return mComponentName.getPackageName().equals(packageName) + && mComponentName.getClassName().equals(className); + } + + public String getFlattenedComponentName() { + return mComponentName.flattenToShortString(); + } + + public void start() { + if (!mRunning) { + if (DEBUG) { + Slog.d(TAG, this + ": Starting"); + } + + mRunning = true; + updateBinding(); + } + } + + public void stop() { + if (mRunning) { + if (DEBUG) { + Slog.d(TAG, this + ": Stopping"); + } + + mRunning = false; + updateBinding(); + } + } + + public void rebindIfDisconnected() { + if (mActiveConnection == null && shouldBind()) { + unbind(); + bind(); + } + } + + private void updateBinding() { + if (shouldBind()) { + bind(); + } else { + unbind(); + } + } + + private boolean shouldBind() { + if (mRunning) { + // Bind whenever there is a discovery request or selected display. + if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE + || mSelectedDisplayId != null) { + return true; + } + } + return false; + } + + private void bind() { + if (!mBound) { + if (DEBUG) { + Slog.d(TAG, this + ": Binding"); + } + + Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE); + service.setComponent(mComponentName); + try { + mBound = mContext.bindServiceAsUser(service, this, Context.BIND_AUTO_CREATE, + new UserHandle(mUserId)); + if (!mBound && DEBUG) { + Slog.d(TAG, this + ": Bind failed"); + } + } catch (SecurityException ex) { + if (DEBUG) { + Slog.d(TAG, this + ": Bind failed", ex); + } + } + } + } + + private void unbind() { + if (mBound) { + if (DEBUG) { + Slog.d(TAG, this + ": Unbinding"); + } + + mBound = false; + disconnect(); + mContext.unbindService(this); + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) { + Slog.d(TAG, this + ": Connected"); + } + + if (mBound) { + disconnect(); + + IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service); + if (provider != null) { + Connection connection = new Connection(provider); + if (connection.register()) { + mActiveConnection = connection; + } else { + if (DEBUG) { + Slog.d(TAG, this + ": Registration failed"); + } + } + } else { + Slog.e(TAG, this + ": Service returned invalid remote display provider binder"); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) { + Slog.d(TAG, this + ": Service disconnected"); + } + disconnect(); + } + + private void onConnectionReady(Connection connection) { + if (mActiveConnection == connection) { + mConnectionReady = true; + + if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) { + mActiveConnection.setDiscoveryMode(mDiscoveryMode); + } + if (mSelectedDisplayId != null) { + mActiveConnection.connect(mSelectedDisplayId); + } + } + } + + private void onConnectionDied(Connection connection) { + if (mActiveConnection == connection) { + if (DEBUG) { + Slog.d(TAG, this + ": Service connection died"); + } + disconnect(); + } + } + + private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) { + if (mActiveConnection == connection) { + if (DEBUG) { + Slog.d(TAG, this + ": State changed, state=" + state); + } + setDisplayState(state); + } + } + + private void disconnect() { + if (mActiveConnection != null) { + if (mSelectedDisplayId != null) { + mActiveConnection.disconnect(mSelectedDisplayId); + } + mConnectionReady = false; + mActiveConnection.dispose(); + mActiveConnection = null; + setDisplayState(null); + } + } + + private void setDisplayState(RemoteDisplayState state) { + if (!Objects.equal(mDisplayState, state)) { + mDisplayState = state; + if (!mScheduledDisplayStateChangedCallback) { + mScheduledDisplayStateChangedCallback = true; + mHandler.post(mDisplayStateChanged); + } + } + } + + @Override + public String toString() { + return "Service connection " + mComponentName.flattenToShortString(); + } + + private final Runnable mDisplayStateChanged = new Runnable() { + @Override + public void run() { + mScheduledDisplayStateChangedCallback = false; + if (mDisplayStateCallback != null) { + mDisplayStateCallback.onDisplayStateChanged( + RemoteDisplayProviderProxy.this, mDisplayState); + } + } + }; + + public interface Callback { + void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state); + } + + private final class Connection implements DeathRecipient { + private final IRemoteDisplayProvider mProvider; + private final ProviderCallback mCallback; + + public Connection(IRemoteDisplayProvider provider) { + mProvider = provider; + mCallback = new ProviderCallback(this); + } + + public boolean register() { + try { + mProvider.asBinder().linkToDeath(this, 0); + mProvider.setCallback(mCallback); + mHandler.post(new Runnable() { + @Override + public void run() { + onConnectionReady(Connection.this); + } + }); + return true; + } catch (RemoteException ex) { + binderDied(); + } + return false; + } + + public void dispose() { + mProvider.asBinder().unlinkToDeath(this, 0); + mCallback.dispose(); + } + + public void setDiscoveryMode(int mode) { + try { + mProvider.setDiscoveryMode(mode); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex); + } + } + + public void connect(String id) { + try { + mProvider.connect(id); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to connect to display.", ex); + } + } + + public void disconnect(String id) { + try { + mProvider.disconnect(id); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex); + } + } + + public void setVolume(String id, int volume) { + try { + mProvider.setVolume(id, volume); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to set display volume.", ex); + } + } + + public void adjustVolume(String id, int volume) { + try { + mProvider.adjustVolume(id, volume); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex); + } + } + + @Override + public void binderDied() { + mHandler.post(new Runnable() { + @Override + public void run() { + onConnectionDied(Connection.this); + } + }); + } + + void postStateChanged(final RemoteDisplayState state) { + mHandler.post(new Runnable() { + @Override + public void run() { + onDisplayStateChanged(Connection.this, state); + } + }); + } + } + + /** + * Receives callbacks from the service. + * <p> + * This inner class is static and only retains a weak reference to the connection + * to prevent the client from being leaked in case the service is holding an + * active reference to the client's callback. + * </p> + */ + private static final class ProviderCallback extends IRemoteDisplayCallback.Stub { + private final WeakReference<Connection> mConnectionRef; + + public ProviderCallback(Connection connection) { + mConnectionRef = new WeakReference<Connection>(connection); + } + + public void dispose() { + mConnectionRef.clear(); + } + + @Override + public void onStateChanged(RemoteDisplayState state) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.postStateChanged(state); + } + } + } +} diff --git a/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java new file mode 100644 index 000000000000..6a5f563683a3 --- /dev/null +++ b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.media.RemoteDisplayState; +import android.os.Handler; +import android.os.UserHandle; +import android.util.Log; +import android.util.Slog; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Watches for remote display provider services to be installed. + * Adds a provider to the media router for each registered service. + * + * @see RemoteDisplayProviderProxy + */ +public final class RemoteDisplayProviderWatcher { + private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Context mContext; + private final Callback mCallback; + private final Handler mHandler; + private final int mUserId; + private final PackageManager mPackageManager; + + private final ArrayList<RemoteDisplayProviderProxy> mProviders = + new ArrayList<RemoteDisplayProviderProxy>(); + private boolean mRunning; + + public RemoteDisplayProviderWatcher(Context context, + Callback callback, Handler handler, int userId) { + mContext = context; + mCallback = callback; + mHandler = handler; + mUserId = userId; + mPackageManager = context.getPackageManager(); + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "Watcher"); + pw.println(prefix + " mUserId=" + mUserId); + pw.println(prefix + " mRunning=" + mRunning); + pw.println(prefix + " mProviders.size()=" + mProviders.size()); + } + + public void start() { + if (!mRunning) { + mRunning = true; + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); + filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + filter.addDataScheme("package"); + mContext.registerReceiverAsUser(mScanPackagesReceiver, + new UserHandle(mUserId), filter, null, mHandler); + + // Scan packages. + // Also has the side-effect of restarting providers if needed. + mHandler.post(mScanPackagesRunnable); + } + } + + public void stop() { + if (mRunning) { + mRunning = false; + + mContext.unregisterReceiver(mScanPackagesReceiver); + mHandler.removeCallbacks(mScanPackagesRunnable); + + // Stop all providers. + for (int i = mProviders.size() - 1; i >= 0; i--) { + mProviders.get(i).stop(); + } + } + } + + private void scanPackages() { + if (!mRunning) { + return; + } + + // Add providers for all new services. + // Reorder the list so that providers left at the end will be the ones to remove. + int targetIndex = 0; + Intent intent = new Intent(RemoteDisplayState.SERVICE_INTERFACE); + for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser( + intent, 0, mUserId)) { + ServiceInfo serviceInfo = resolveInfo.serviceInfo; + if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) { + int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); + if (sourceIndex < 0) { + RemoteDisplayProviderProxy provider = + new RemoteDisplayProviderProxy(mContext, + new ComponentName(serviceInfo.packageName, serviceInfo.name), + mUserId); + provider.start(); + mProviders.add(targetIndex++, provider); + mCallback.addProvider(provider); + } else if (sourceIndex >= targetIndex) { + RemoteDisplayProviderProxy provider = mProviders.get(sourceIndex); + provider.start(); // restart the provider if needed + provider.rebindIfDisconnected(); + Collections.swap(mProviders, sourceIndex, targetIndex++); + } + } + } + + // Remove providers for missing services. + if (targetIndex < mProviders.size()) { + for (int i = mProviders.size() - 1; i >= targetIndex; i--) { + RemoteDisplayProviderProxy provider = mProviders.get(i); + mCallback.removeProvider(provider); + mProviders.remove(provider); + provider.stop(); + } + } + } + + private boolean verifyServiceTrusted(ServiceInfo serviceInfo) { + if (serviceInfo.permission == null || !serviceInfo.permission.equals( + Manifest.permission.BIND_REMOTE_DISPLAY)) { + // If the service does not require this permission then any app could + // potentially bind to it and cause the remote display service to + // misbehave. So we only want to trust providers that require the + // correct permissions. + Slog.w(TAG, "Ignoring remote display provider service because it did not " + + "require the BIND_REMOTE_DISPLAY permission in its manifest: " + + serviceInfo.packageName + "/" + serviceInfo.name); + return false; + } + if (!hasCaptureVideoPermission(serviceInfo.packageName)) { + // If the service does not have permission to capture video then it + // isn't going to be terribly useful as a remote display, is it? + // Kind of makes you wonder what it's doing there in the first place. + Slog.w(TAG, "Ignoring remote display provider service because it does not " + + "have the CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT " + + "permission: " + serviceInfo.packageName + "/" + serviceInfo.name); + return false; + } + // Looks good. + return true; + } + + private boolean hasCaptureVideoPermission(String packageName) { + if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT, + packageName) == PackageManager.PERMISSION_GRANTED) { + return true; + } + if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT, + packageName) == PackageManager.PERMISSION_GRANTED) { + return true; + } + return false; + } + + private int findProvider(String packageName, String className) { + int count = mProviders.size(); + for (int i = 0; i < count; i++) { + RemoteDisplayProviderProxy provider = mProviders.get(i); + if (provider.hasComponentName(packageName, className)) { + return i; + } + } + return -1; + } + + private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, "Received package manager broadcast: " + intent); + } + scanPackages(); + } + }; + + private final Runnable mScanPackagesRunnable = new Runnable() { + @Override + public void run() { + scanPackages(); + } + }; + + public interface Callback { + void addProvider(RemoteDisplayProviderProxy provider); + void removeProvider(RemoteDisplayProviderProxy provider); + } +} diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 3d6b3c92dc1a..dc63ca27c7db 100755 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -217,6 +217,7 @@ public class PackageManagerService extends IPackageManager.Stub { static final int SCAN_UPDATE_TIME = 1<<6; static final int SCAN_DEFER_DEX = 1<<7; static final int SCAN_BOOTING = 1<<8; + static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<9; static final int REMOVE_CHATTY = 1<<16; @@ -332,7 +333,6 @@ public class PackageManagerService extends IPackageManager.Stub { new HashMap<String, PackageParser.Package>(); // Information for the parser to write more useful error messages. - File mScanningPath; int mLastScanError; // ---------------------------------------------------------------- @@ -867,7 +867,7 @@ public class PackageManagerService extends IPackageManager.Stub { int[] uidArray = new int[] { res.pkg.applicationInfo.uid }; ArrayList<String> pkgList = new ArrayList<String>(1); pkgList.add(res.pkg.applicationInfo.packageName); - sendResourcesChangedBroadcast(true, false, + sendResourcesChangedBroadcast(true, true, pkgList,uidArray, null); } } @@ -1278,7 +1278,8 @@ public class PackageManagerService extends IPackageManager.Stub { frameworkDir.getPath(), OBSERVER_EVENTS, true, false); mFrameworkInstallObserver.startWatching(); scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR, + | PackageParser.PARSE_IS_SYSTEM_DIR + | PackageParser.PARSE_IS_PRIVILEGED, scanMode | SCAN_NO_DEX, 0); // Collected privileged system packages. @@ -1771,6 +1772,24 @@ public class PackageManagerService extends IPackageManager.Stub { state, userId); } + public boolean isPackageAvailable(String packageName, int userId) { + if (!sUserManager.exists(userId)) return false; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "is package available"); + synchronized (mPackages) { + PackageParser.Package p = mPackages.get(packageName); + if (p != null) { + final PackageSetting ps = (PackageSetting) p.mExtras; + if (ps != null) { + final PackageUserState state = ps.readUserState(userId); + if (state != null) { + return PackageParser.isAvailable(state); + } + } + } + } + return false; + } + @Override public PackageInfo getPackageInfo(String packageName, int flags, int userId) { if (!sUserManager.exists(userId)) return null; @@ -3638,6 +3657,12 @@ public class PackageManagerService extends IPackageManager.Stub { // An updated system app will not have the PARSE_IS_SYSTEM flag set // initially parseFlags |= PackageParser.PARSE_IS_SYSTEM; + + // An updated privileged app will not have the PARSE_IS_PRIVILEGED + // flag set initially + if ((updatedPkg.pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0) { + parseFlags |= PackageParser.PARSE_IS_PRIVILEGED; + } } // Verify certificates against what was last scanned if (!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags)) { @@ -4119,7 +4144,6 @@ public class PackageManagerService extends IPackageManager.Stub { mLastScanError = PackageManager.INSTALL_FAILED_INVALID_APK; return null; } - mScanningPath = scanFile; if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; @@ -4139,7 +4163,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (mAndroidApplication != null) { Slog.w(TAG, "*************************************************"); Slog.w(TAG, "Core android package being redefined. Skipping."); - Slog.w(TAG, " file=" + mScanningPath); + Slog.w(TAG, " file=" + scanFile); Slog.w(TAG, "*************************************************"); mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; return null; @@ -4619,6 +4643,10 @@ public class PackageManagerService extends IPackageManager.Stub { if ((scanMode&SCAN_NO_DEX) == 0) { if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) { + if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) { + removeDataDirsLI(pkg.packageName); + } + mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT; return null; } @@ -4696,6 +4724,10 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Package clientPkg = clientLibPkgs.get(i); if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) { + if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) { + removeDataDirsLI(pkg.packageName); + } + mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT; return null; } @@ -4959,6 +4991,18 @@ public class PackageManagerService extends IPackageManager.Stub { permissionMap.put(p.info.name, bp); } if (bp.perm == null) { + if (bp.sourcePackage != null + && !bp.sourcePackage.equals(p.info.packageName)) { + // If this is a permission that was formerly defined by a non-system + // app, but is now defined by a system app (following an upgrade), + // discard the previous declaration and consider the system's to be + // canonical. + if (isSystemApp(p.owner)) { + Slog.i(TAG, "New decl " + p.owner + " of permission " + + p.info.name + " is system"); + bp.sourcePackage = null; + } + } if (bp.sourcePackage == null || bp.sourcePackage.equals(p.info.packageName)) { BasePermission tree = findPermissionTreeLP(p.info.name); @@ -9038,7 +9082,7 @@ public class PackageManagerService extends IPackageManager.Stub { replacePackageLI(pkg, parseFlags, scanMode, args.user, installerPackageName, res); } else { - installNewPackageLI(pkg, parseFlags, scanMode, args.user, + installNewPackageLI(pkg, parseFlags, scanMode | SCAN_DELETE_DATA_ON_FAILURES, args.user, installerPackageName, res); } synchronized (mPackages) { @@ -9999,11 +10043,11 @@ public class PackageManagerService extends IPackageManager.Stub { } if (filter.countDataAuthorities() != 0 || filter.countDataPaths() != 0 - || filter.countDataSchemes() != 0 + || filter.countDataSchemes() > 1 || filter.countDataTypes() != 0) { throw new IllegalArgumentException( "replacePreferredActivity expects filter to have no data authorities, " + - "paths, schemes or types."); + "paths, or types; and at most one scheme."); } synchronized (mPackages) { if (mContext.checkCallingOrSelfPermission( @@ -10020,33 +10064,27 @@ public class PackageManagerService extends IPackageManager.Stub { } final int callingUserId = UserHandle.getCallingUserId(); - ArrayList<PreferredActivity> removed = null; PreferredIntentResolver pir = mSettings.mPreferredActivities.get(callingUserId); if (pir != null) { - Iterator<PreferredActivity> it = pir.filterIterator(); - String action = filter.getAction(0); - String category = filter.getCategory(0); - while (it.hasNext()) { - PreferredActivity pa = it.next(); - if ((pa.countActions() == 0) || (pa.countCategories() == 0) - || (pa.getAction(0).equals(action) - && pa.getCategory(0).equals(category))) { - if (removed == null) { - removed = new ArrayList<PreferredActivity>(); - } - removed.add(pa); - if (DEBUG_PREFERRED) { - Slog.i(TAG, "Removing preferred activity " - + pa.mPref.mComponent + ":"); - filter.dump(new LogPrinter(Log.INFO, TAG), " "); - } - } - } - if (removed != null) { - for (int i=0; i<removed.size(); i++) { - PreferredActivity pa = removed.get(i); - pir.removeFilter(pa); + Intent intent = new Intent(filter.getAction(0)).addCategory(filter.getCategory(0)); + if (filter.countDataSchemes() == 1) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(filter.getDataScheme(0)); + intent.setData(builder.build()); + } + List<PreferredActivity> matches = pir.queryIntent( + intent, null, true, callingUserId); + if (DEBUG_PREFERRED) { + Slog.i(TAG, matches.size() + " preferred matches for " + intent); + } + for (int i = 0; i < matches.size(); i++) { + PreferredActivity pa = matches.get(i); + if (DEBUG_PREFERRED) { + Slog.i(TAG, "Removing preferred activity " + + pa.mPref.mComponent + ":"); + filter.dump(new LogPrinter(Log.INFO, TAG), " "); } + pir.removeFilter(pa); } } addPreferredActivityInternal(filter, match, set, activity, true, callingUserId); @@ -10551,7 +10589,8 @@ public class PackageManagerService extends IPackageManager.Stub { DumpState dumpState = new DumpState(); boolean fullPreferred = false; - + boolean checkin = false; + String packageName = null; int opti = 0; @@ -10565,7 +10604,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Right now we only know how to print all. } else if ("-h".equals(opt)) { pw.println("Package manager dump options:"); - pw.println(" [-h] [-f] [cmd] ..."); + pw.println(" [-h] [-f] [--checkin] [cmd] ..."); + pw.println(" --checkin: dump for a checkin"); pw.println(" -f: print details of intent filters"); pw.println(" -h: print this help"); pw.println(" cmd may be one of:"); @@ -10583,13 +10623,15 @@ public class PackageManagerService extends IPackageManager.Stub { pw.println(" <package.name>: info about given package"); pw.println(" k[eysets]: print known keysets"); return; + } else if ("--checkin".equals(opt)) { + checkin = true; } else if ("-f".equals(opt)) { dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS); } else { pw.println("Unknown argument: " + opt + "; use -h for help"); } } - + // Is the caller requesting to dump a particular piece of data? if (opti < args.length) { String cmd = args[opti]; @@ -10631,17 +10673,26 @@ public class PackageManagerService extends IPackageManager.Stub { } } + if (checkin) { + pw.println("vers,1"); + } + // reader synchronized (mPackages) { if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) { - if (dumpState.onTitlePrinted()) - pw.println(); - pw.println("Verifiers:"); - pw.print(" Required: "); - pw.print(mRequiredVerifierPackage); - pw.print(" (uid="); - pw.print(getPackageUid(mRequiredVerifierPackage, 0)); - pw.println(")"); + if (!checkin) { + if (dumpState.onTitlePrinted()) + pw.println(); + pw.println("Verifiers:"); + pw.print(" Required: "); + pw.print(mRequiredVerifierPackage); + pw.print(" (uid="); + pw.print(getPackageUid(mRequiredVerifierPackage, 0)); + pw.println(")"); + } else if (mRequiredVerifierPackage != null) { + pw.print("vrfy,"); pw.print(mRequiredVerifierPackage); + pw.print(","); pw.println(getPackageUid(mRequiredVerifierPackage, 0)); + } } if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) { @@ -10650,21 +10701,37 @@ public class PackageManagerService extends IPackageManager.Stub { while (it.hasNext()) { String name = it.next(); SharedLibraryEntry ent = mSharedLibraries.get(name); - if (!printedHeader) { - if (dumpState.onTitlePrinted()) - pw.println(); - pw.println("Libraries:"); - printedHeader = true; + if (!checkin) { + if (!printedHeader) { + if (dumpState.onTitlePrinted()) + pw.println(); + pw.println("Libraries:"); + printedHeader = true; + } + pw.print(" "); + } else { + pw.print("lib,"); } - pw.print(" "); pw.print(name); - pw.print(" -> "); + if (!checkin) { + pw.print(" -> "); + } if (ent.path != null) { - pw.print("(jar) "); - pw.print(ent.path); + if (!checkin) { + pw.print("(jar) "); + pw.print(ent.path); + } else { + pw.print(",jar,"); + pw.print(ent.path); + } } else { - pw.print("(apk) "); - pw.print(ent.apk); + if (!checkin) { + pw.print("(apk) "); + pw.print(ent.apk); + } else { + pw.print(",apk,"); + pw.print(ent.apk); + } } pw.println(); } @@ -10673,16 +10740,22 @@ public class PackageManagerService extends IPackageManager.Stub { if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) { if (dumpState.onTitlePrinted()) pw.println(); - pw.println("Features:"); + if (!checkin) { + pw.println("Features:"); + } Iterator<String> it = mAvailableFeatures.keySet().iterator(); while (it.hasNext()) { String name = it.next(); - pw.print(" "); + if (!checkin) { + pw.print(" "); + } else { + pw.print("feat,"); + } pw.println(name); } } - if (dumpState.isDumping(DumpState.DUMP_RESOLVERS)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_RESOLVERS)) { if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:" : "Activity Resolver Table:", " ", packageName, dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { @@ -10705,7 +10778,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - if (dumpState.isDumping(DumpState.DUMP_PREFERRED)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED)) { for (int i=0; i<mSettings.mPreferredActivities.size(); i++) { PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i); int user = mSettings.mPreferredActivities.keyAt(i); @@ -10719,7 +10792,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - if (dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)) { pw.flush(); FileOutputStream fout = new FileOutputStream(fd); BufferedOutputStream str = new BufferedOutputStream(fout); @@ -10741,11 +10814,11 @@ public class PackageManagerService extends IPackageManager.Stub { } } - if (dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) { mSettings.dumpPermissionsLPr(pw, packageName, dumpState); } - if (dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { boolean printedSomething = false; for (PackageParser.Provider p : mProviders.mProviders.values()) { if (packageName != null && !packageName.equals(p.info.packageName)) { @@ -10782,19 +10855,19 @@ public class PackageManagerService extends IPackageManager.Stub { } } - if (dumpState.isDumping(DumpState.DUMP_KEYSETS)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_KEYSETS)) { mSettings.mKeySetManager.dump(pw, packageName, dumpState); } if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) { - mSettings.dumpPackagesLPr(pw, packageName, dumpState); + mSettings.dumpPackagesLPr(pw, packageName, dumpState, checkin); } - if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) { mSettings.dumpSharedUsersLPr(pw, packageName, dumpState); } - if (dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) { if (dumpState.onTitlePrinted()) pw.println(); mSettings.dumpReadMessagesLPr(pw, dumpState); @@ -11033,7 +11106,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (uidArr != null) { extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr); } - if (replacing && !mediaStatus) { + if (replacing) { extras.putBoolean(Intent.EXTRA_REPLACING, replacing); } String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index d3ccba6fda4f..19bfe01600fd 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -1959,10 +1959,14 @@ final class Settings { } boolean doNonData = true; + boolean hasSchemes = false; for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) { boolean doScheme = true; String scheme = tmpPa.getDataScheme(ischeme); + if (scheme != null && !scheme.isEmpty()) { + hasSchemes = true; + } for (int issp=0; issp<tmpPa.countDataSchemeSpecificParts(); issp++) { Uri.Builder builder = new Uri.Builder(); builder.scheme(scheme); @@ -2016,11 +2020,25 @@ final class Settings { } for (int idata=0; idata<tmpPa.countDataTypes(); idata++) { - Intent finalIntent = new Intent(intent); String mimeType = tmpPa.getDataType(idata); - finalIntent.setType(mimeType); - applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, - null, null, null, null, mimeType, userId); + if (hasSchemes) { + Uri.Builder builder = new Uri.Builder(); + for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) { + String scheme = tmpPa.getDataScheme(ischeme); + if (scheme != null && !scheme.isEmpty()) { + Intent finalIntent = new Intent(intent); + builder.scheme(scheme); + finalIntent.setDataAndType(builder.build(), mimeType); + applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, + scheme, null, null, null, mimeType, userId); + } + } + } else { + Intent finalIntent = new Intent(intent); + finalIntent.setType(mimeType); + applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, + null, null, null, null, mimeType, userId); + } doNonData = false; } @@ -2873,8 +2891,44 @@ final class Settings { ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE", }; - void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf, - Date date, List<UserInfo> users) { + void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag, PackageSetting ps, + SimpleDateFormat sdf, Date date, List<UserInfo> users) { + if (checkinTag != null) { + pw.print(checkinTag); + pw.print(","); + pw.print(ps.realName != null ? ps.realName : ps.name); + pw.print(","); + pw.print(ps.appId); + pw.print(","); + pw.print(ps.versionCode); + pw.print(","); + pw.print(ps.firstInstallTime); + pw.print(","); + pw.print(ps.lastUpdateTime); + pw.print(","); + pw.print(ps.installerPackageName != null ? ps.installerPackageName : "?"); + pw.println(); + for (UserInfo user : users) { + pw.print(checkinTag); + pw.print("-"); + pw.print("usr"); + pw.print(","); + pw.print(user.id); + pw.print(","); + pw.print(ps.getInstalled(user.id) ? "I" : "i"); + pw.print(ps.getBlocked(user.id) ? "B" : "b"); + pw.print(ps.getStopped(user.id) ? "S" : "s"); + pw.print(ps.getNotLaunched(user.id) ? "l" : "L"); + pw.print(","); + pw.print(ps.getEnabled(user.id)); + String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id); + pw.print(","); + pw.print(lastDisabledAppCaller != null ? lastDisabledAppCaller : "?"); + pw.println(); + } + return; + } + pw.print(prefix); pw.print("Package ["); pw.print(ps.realName != null ? ps.realName : ps.name); pw.print("] ("); @@ -3036,7 +3090,7 @@ final class Settings { } } - void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) { + void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState, boolean checkin) { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); final Date date = new Date(); boolean printedSomething = false; @@ -3047,35 +3101,39 @@ final class Settings { continue; } - if (packageName != null) { + if (!checkin && packageName != null) { dumpState.setSharedUser(ps.sharedUser); } - if (!printedSomething) { + if (!checkin && !printedSomething) { if (dumpState.onTitlePrinted()) pw.println(); pw.println("Packages:"); printedSomething = true; } - dumpPackageLPr(pw, " ", ps, sdf, date, users); + dumpPackageLPr(pw, " ", checkin ? "pkg" : null, ps, sdf, date, users); } printedSomething = false; - if (mRenamedPackages.size() > 0) { + if (!checkin && mRenamedPackages.size() > 0) { for (final Map.Entry<String, String> e : mRenamedPackages.entrySet()) { if (packageName != null && !packageName.equals(e.getKey()) && !packageName.equals(e.getValue())) { continue; } - if (!printedSomething) { - if (dumpState.onTitlePrinted()) - pw.println(); - pw.println("Renamed packages:"); - printedSomething = true; + if (!checkin) { + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(); + pw.println("Renamed packages:"); + printedSomething = true; + } + pw.print(" "); + } else { + pw.print("ren,"); } - pw.print(" "); pw.print(e.getKey()); - pw.print(" -> "); + pw.print(checkin ? " -> " : ","); pw.println(e.getValue()); } } @@ -3087,13 +3145,13 @@ final class Settings { && !packageName.equals(ps.name)) { continue; } - if (!printedSomething) { + if (!checkin && !printedSomething) { if (dumpState.onTitlePrinted()) pw.println(); pw.println("Hidden system packages:"); printedSomething = true; } - dumpPackageLPr(pw, " ", ps, sdf, date, users); + dumpPackageLPr(pw, " ", checkin ? "dis" : null, ps, sdf, date, users); } } } diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index 976a328c4ecb..30bc9226da24 100644 --- a/services/java/com/android/server/power/DisplayPowerController.java +++ b/services/java/com/android/server/power/DisplayPowerController.java @@ -298,6 +298,10 @@ final class DisplayPowerController { // True if mAmbientLux holds a valid value. private boolean mAmbientLuxValid; + // The ambient light level threshold at which to brighten or darken the screen. + private float mBrighteningLuxThreshold; + private float mDarkeningLuxThreshold; + // The most recent light sample. private float mLastObservedLux; @@ -606,7 +610,6 @@ final class DisplayPowerController { && mProximity == PROXIMITY_POSITIVE) { mScreenOffBecauseOfProximity = true; sendOnProximityPositiveWithWakelock(); - setScreenOn(false); } } else if (mWaitingForNegativeProximity && mScreenOffBecauseOfProximity @@ -666,59 +669,62 @@ final class DisplayPowerController { mUsingScreenAutoBrightness = false; } - // Animate the screen on or off. - if (!mScreenOffBecauseOfProximity) { - if (wantScreenOn(mPowerRequest.screenState)) { - // Want screen on. - // Wait for previous off animation to complete beforehand. - // It is relatively short but if we cancel it and switch to the - // on animation immediately then the results are pretty ugly. - if (!mElectronBeamOffAnimator.isStarted()) { - // Turn the screen on. The contents of the screen may not yet - // be visible if the electron beam has not been dismissed because - // its last frame of animation is solid black. - setScreenOn(true); - - if (mPowerRequest.blockScreenOn - && mPowerState.getElectronBeamLevel() == 0.0f) { - blockScreenOn(); - } else { - unblockScreenOn(); - if (USE_ELECTRON_BEAM_ON_ANIMATION) { - if (!mElectronBeamOnAnimator.isStarted()) { - if (mPowerState.getElectronBeamLevel() == 1.0f) { - mPowerState.dismissElectronBeam(); - } else if (mPowerState.prepareElectronBeam( - mElectronBeamFadesConfig ? - ElectronBeam.MODE_FADE : - ElectronBeam.MODE_WARM_UP)) { - mElectronBeamOnAnimator.start(); - } else { - mElectronBeamOnAnimator.end(); - } + // Animate the screen on or off unless blocked. + if (mScreenOffBecauseOfProximity) { + // Screen off due to proximity. + setScreenOn(false); + unblockScreenOn(); + } else if (wantScreenOn(mPowerRequest.screenState)) { + // Want screen on. + // Wait for previous off animation to complete beforehand. + // It is relatively short but if we cancel it and switch to the + // on animation immediately then the results are pretty ugly. + if (!mElectronBeamOffAnimator.isStarted()) { + // Turn the screen on. The contents of the screen may not yet + // be visible if the electron beam has not been dismissed because + // its last frame of animation is solid black. + setScreenOn(true); + + if (mPowerRequest.blockScreenOn + && mPowerState.getElectronBeamLevel() == 0.0f) { + blockScreenOn(); + } else { + unblockScreenOn(); + if (USE_ELECTRON_BEAM_ON_ANIMATION) { + if (!mElectronBeamOnAnimator.isStarted()) { + if (mPowerState.getElectronBeamLevel() == 1.0f) { + mPowerState.dismissElectronBeam(); + } else if (mPowerState.prepareElectronBeam( + mElectronBeamFadesConfig ? + ElectronBeam.MODE_FADE : + ElectronBeam.MODE_WARM_UP)) { + mElectronBeamOnAnimator.start(); + } else { + mElectronBeamOnAnimator.end(); } - } else { - mPowerState.setElectronBeamLevel(1.0f); - mPowerState.dismissElectronBeam(); } + } else { + mPowerState.setElectronBeamLevel(1.0f); + mPowerState.dismissElectronBeam(); } } - } else { - // Want screen off. - // Wait for previous on animation to complete beforehand. - if (!mElectronBeamOnAnimator.isStarted()) { - if (!mElectronBeamOffAnimator.isStarted()) { - if (mPowerState.getElectronBeamLevel() == 0.0f) { - setScreenOn(false); - } else if (mPowerState.prepareElectronBeam( - mElectronBeamFadesConfig ? - ElectronBeam.MODE_FADE : - ElectronBeam.MODE_COOL_DOWN) - && mPowerState.isScreenOn()) { - mElectronBeamOffAnimator.start(); - } else { - mElectronBeamOffAnimator.end(); - } + } + } else { + // Want screen off. + // Wait for previous on animation to complete beforehand. + unblockScreenOn(); + if (!mElectronBeamOnAnimator.isStarted()) { + if (!mElectronBeamOffAnimator.isStarted()) { + if (mPowerState.getElectronBeamLevel() == 0.0f) { + setScreenOn(false); + } else if (mPowerState.prepareElectronBeam( + mElectronBeamFadesConfig ? + ElectronBeam.MODE_FADE : + ElectronBeam.MODE_COOL_DOWN) + && mPowerState.isScreenOn()) { + mElectronBeamOffAnimator.start(); + } else { + mElectronBeamOffAnimator.end(); } } } @@ -758,15 +764,15 @@ final class DisplayPowerController { private void unblockScreenOn() { if (mScreenOnWasBlocked) { mScreenOnWasBlocked = false; - if (DEBUG) { - Slog.d(TAG, "Unblocked screen on after " + - (SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime) + " ms"); + long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime; + if (delay > 1000 || DEBUG) { + Slog.d(TAG, "Unblocked screen on after " + delay + " ms"); } } } private void setScreenOn(boolean on) { - if (!mPowerState.isScreenOn() == on) { + if (mPowerState.isScreenOn() != on) { mPowerState.setScreenOn(on); if (on) { mNotifier.onScreenOn(); @@ -945,12 +951,24 @@ final class DisplayPowerController { mLastObservedLuxTime = time; } + private void setAmbientLux(float lux) { + mAmbientLux = lux; + mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS); + mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS); + } + private void updateAmbientLux(long time) { // If the light sensor was just turned on then immediately update our initial // estimate of the current ambient light level. - if (!mAmbientLuxValid - || (time - mLightSensorEnableTime) < mLightSensorWarmUpTimeConfig) { - mAmbientLux = mRecentShortTermAverageLux; + if (!mAmbientLuxValid) { + final long timeWhenSensorWarmedUp = + mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; + if (time < timeWhenSensorWarmedUp) { + mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, + timeWhenSensorWarmedUp); + return; + } + setAmbientLux(mRecentShortTermAverageLux); mAmbientLuxValid = true; mDebounceLuxDirection = 0; mDebounceLuxTime = time; @@ -961,98 +979,90 @@ final class DisplayPowerController { + ", mAmbientLux=" + mAmbientLux); } updateAutoBrightness(true); - return; - } - - // Determine whether the ambient environment appears to be brightening. - float brighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS); - if (mRecentShortTermAverageLux > brighteningLuxThreshold - && mRecentLongTermAverageLux > brighteningLuxThreshold) { + } else if (mRecentShortTermAverageLux > mBrighteningLuxThreshold + && mRecentLongTermAverageLux > mBrighteningLuxThreshold) { + // The ambient environment appears to be brightening. if (mDebounceLuxDirection <= 0) { mDebounceLuxDirection = 1; mDebounceLuxTime = time; if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Possibly brightened, waiting for " + BRIGHTENING_LIGHT_DEBOUNCE + " ms: " - + "brighteningLuxThreshold=" + brighteningLuxThreshold + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + ", mAmbientLux=" + mAmbientLux); } } long debounceTime = mDebounceLuxTime + BRIGHTENING_LIGHT_DEBOUNCE; - if (time >= debounceTime) { - mAmbientLux = mRecentShortTermAverageLux; - if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Brightened: " - + "brighteningLuxThreshold=" + brighteningLuxThreshold - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux - + ", mAmbientLux=" + mAmbientLux); - } - updateAutoBrightness(true); - } else { + if (time < debounceTime) { mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime); + return; } - return; - } - - // Determine whether the ambient environment appears to be darkening. - float darkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS); - if (mRecentShortTermAverageLux < darkeningLuxThreshold - && mRecentLongTermAverageLux < darkeningLuxThreshold) { + setAmbientLux(mRecentShortTermAverageLux); + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Brightened: " + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true); + } else if (mRecentShortTermAverageLux < mDarkeningLuxThreshold + && mRecentLongTermAverageLux < mDarkeningLuxThreshold) { + // The ambient environment appears to be darkening. if (mDebounceLuxDirection >= 0) { mDebounceLuxDirection = -1; mDebounceLuxTime = time; if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Possibly darkened, waiting for " + DARKENING_LIGHT_DEBOUNCE + " ms: " - + "darkeningLuxThreshold=" + darkeningLuxThreshold + + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + ", mAmbientLux=" + mAmbientLux); } } long debounceTime = mDebounceLuxTime + DARKENING_LIGHT_DEBOUNCE; - if (time >= debounceTime) { - // Be conservative about reducing the brightness, only reduce it a little bit - // at a time to avoid having to bump it up again soon. - mAmbientLux = Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux); - if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Darkened: " - + "darkeningLuxThreshold=" + darkeningLuxThreshold - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux - + ", mAmbientLux=" + mAmbientLux); - } - updateAutoBrightness(true); - } else { + if (time < debounceTime) { mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime); + return; } - return; - } - - // No change or change is within the hysteresis thresholds. - if (mDebounceLuxDirection != 0) { + // Be conservative about reducing the brightness, only reduce it a little bit + // at a time to avoid having to bump it up again soon. + setAmbientLux(Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux)); + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Darkened: " + + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true); + } else if (mDebounceLuxDirection != 0) { + // No change or change is within the hysteresis thresholds. mDebounceLuxDirection = 0; mDebounceLuxTime = time; if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Canceled debounce: " - + "brighteningLuxThreshold=" + brighteningLuxThreshold - + ", darkeningLuxThreshold=" + darkeningLuxThreshold + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + + ", mDarkeningLuxThreshold=" + mDarkeningLuxThreshold + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + ", mAmbientLux=" + mAmbientLux); } } - // If the light level does not change, then the sensor may not report - // a new value. This can cause problems for the auto-brightness algorithm - // because the filters might not be updated. To work around it, we want to - // make sure to update the filters whenever the observed light level could - // possibly exceed one of the hysteresis thresholds. - if (mLastObservedLux > brighteningLuxThreshold - || mLastObservedLux < darkeningLuxThreshold) { + // Now that we've done all of that, we haven't yet posted a debounce + // message. So consider the case where current lux is beyond the + // threshold. It's possible that the light sensor may not report values + // if the light level does not change, so we need to occasionally + // synthesize sensor readings in order to make sure the brightness is + // adjusted accordingly. Note these thresholds may have changed since + // we entered the function because we called setAmbientLux and + // updateAutoBrightness along the way. + if (mLastObservedLux > mBrighteningLuxThreshold + || mLastObservedLux < mDarkeningLuxThreshold) { mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, time + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS); } diff --git a/services/java/com/android/server/power/DisplayPowerState.java b/services/java/com/android/server/power/DisplayPowerState.java index fa318f8b6198..5c048f150a45 100644 --- a/services/java/com/android/server/power/DisplayPowerState.java +++ b/services/java/com/android/server/power/DisplayPowerState.java @@ -304,8 +304,15 @@ final class DisplayPowerState { int brightness = mScreenOn && mElectronBeamLevel > 0f ? mScreenBrightness : 0; if (mPhotonicModulator.setState(mScreenOn, brightness)) { + if (DEBUG) { + Slog.d(TAG, "Screen ready"); + } mScreenReady = true; invokeCleanListenerIfNeeded(); + } else { + if (DEBUG) { + Slog.d(TAG, "Screen not ready"); + } } } }; @@ -355,7 +362,7 @@ final class DisplayPowerState { AsyncTask.THREAD_POOL_EXECUTOR.execute(mTask); } } - return mChangeInProgress; + return !mChangeInProgress; } } diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index da9548f7266e..134718b38c96 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -1135,6 +1135,9 @@ public final class PowerManagerService extends IPowerManager.Stub if (!mSystemReady || mDirty == 0) { return; } + if (!Thread.holdsLock(mLock)) { + Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked"); + } // Phase 0: Basic state updates. updateIsPoweredLocked(mDirty); diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java index 8a3997af1abb..98acc27741fa 100644 --- a/services/java/com/android/server/print/PrintManagerService.java +++ b/services/java/com/android/server/print/PrintManagerService.java @@ -366,7 +366,7 @@ public final class PrintManagerService extends IPrintManager.Stub { pw.println("PRINT MANAGER STATE (dumpsys print)"); final int userStateCount = mUserStates.size(); for (int i = 0; i < userStateCount; i++) { - UserState userState = mUserStates.get(i); + UserState userState = mUserStates.valueAt(i); userState.dump(fd, pw, ""); pw.println(); } diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index 5a60de0f269b..bb61e491c0d3 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -98,6 +98,13 @@ public class UsbDeviceManager { // which need debouncing. private static final int UPDATE_DELAY = 1000; + // Time we received a request to enter USB accessory mode + private long mAccessoryModeRequestTime = 0; + + // Timeout for entering USB request mode. + // Request is cancelled if host does not configure device within 10 seconds. + private static final int ACCESSORY_REQUEST_TIMEOUT = 10 * 1000; + private static final String BOOT_MODE_PROPERTY = "ro.bootmode"; private UsbHandler mHandler; @@ -205,6 +212,8 @@ public class UsbDeviceManager { } private void startAccessoryMode() { + if (!mHasUsbAccessory) return; + mAccessoryStrings = nativeGetAccessoryStrings(); boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE); // don't start accessory mode if our mandatory strings have not been set @@ -223,6 +232,7 @@ public class UsbDeviceManager { } if (functions != null) { + mAccessoryModeRequestTime = SystemClock.elapsedRealtime(); setCurrentFunctions(functions, false); } } @@ -456,6 +466,8 @@ public class UsbDeviceManager { } private void setEnabledFunctions(String functions, boolean makeDefault) { + if (DEBUG) Slog.d(TAG, "setEnabledFunctions " + functions + + " makeDefault: " + makeDefault); // Do not update persystent.sys.usb.config if the device is booted up // with OEM specific mode. @@ -517,9 +529,16 @@ public class UsbDeviceManager { } private void updateCurrentAccessory() { - if (!mHasUsbAccessory) return; + // We are entering accessory mode if we have received a request from the host + // and the request has not timed out yet. + boolean enteringAccessoryMode = + mAccessoryModeRequestTime > 0 && + SystemClock.elapsedRealtime() < + mAccessoryModeRequestTime + ACCESSORY_REQUEST_TIMEOUT; + + if (mConfigured && enteringAccessoryMode) { + // successfully entered accessory mode - if (mConfigured) { if (mAccessoryStrings != null) { mCurrentAccessory = new UsbAccessory(mAccessoryStrings); Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); @@ -530,7 +549,7 @@ public class UsbDeviceManager { } else { Slog.e(TAG, "nativeGetAccessoryStrings failed"); } - } else if (!mConnected) { + } else if (!enteringAccessoryMode) { // make sure accessory mode is off // and restore default functions Slog.d(TAG, "exited USB accessory mode"); @@ -560,6 +579,8 @@ public class UsbDeviceManager { } } + if (DEBUG) Slog.d(TAG, "broadcasting " + intent + " connected: " + mConnected + + " configured: " + mConfigured); mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } @@ -599,9 +620,7 @@ public class UsbDeviceManager { if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ACCESSORY)) { updateCurrentAccessory(); - } - - if (!mConnected) { + } else if (!mConnected) { // restore defaults when USB is disconnected setEnabledFunctions(mDefaultFunctions, false); } diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java index a3d514e304ca..bdb0a5e27f49 100644 --- a/services/java/com/android/server/wifi/WifiController.java +++ b/services/java/com/android/server/wifi/WifiController.java @@ -152,11 +152,21 @@ class WifiController extends StateMachine { addState(mStaDisabledWithScanState, mDefaultState); addState(mApEnabledState, mDefaultState); addState(mEcmState, mDefaultState); - if (mSettingsStore.isScanAlwaysAvailable()) { + + boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn(); + boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled(); + boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable(); + + log("isAirplaneModeOn = " + isAirplaneModeOn + + ", isWifiEnabled = " + isWifiEnabled + + ", isScanningAvailable = " + isScanningAlwaysAvailable); + + if (isWifiEnabled && isScanningAlwaysAvailable) { setInitialState(mStaDisabledWithScanState); } else { setInitialState(mApStaDisabledState); } + setLogRecSize(100); setLogOnlyTransitions(false); diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java index d471b5708eae..aecf9aea5b6a 100644 --- a/services/java/com/android/server/wifi/WifiService.java +++ b/services/java/com/android/server/wifi/WifiService.java @@ -369,15 +369,17 @@ public final class WifiService extends IWifiManager.Stub { } private class BatchedScanRequest extends DeathRecipient { - BatchedScanSettings settings; - int uid; - int pid; + final BatchedScanSettings settings; + final int uid; + final int pid; + final WorkSource workSource; - BatchedScanRequest(BatchedScanSettings settings, IBinder binder) { + BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) { super(0, null, binder, null); this.settings = settings; this.uid = getCallingUid(); this.pid = getCallingPid(); + workSource = ws; } public void binderDied() { stopBatchedScan(settings, uid, pid); @@ -406,12 +408,19 @@ public final class WifiService extends IWifiManager.Stub { /** * see {@link android.net.wifi.WifiManager#requestBatchedScan()} */ - public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder) { + public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder, + WorkSource workSource) { enforceChangePermission(); + if (workSource != null) { + enforceWorkSourcePermission(); + // WifiManager currently doesn't use names, so need to clear names out of the + // supplied WorkSource to allow future WorkSource combining. + workSource.clearNames(); + } if (mBatchedScanSupported == false) return false; requested = new BatchedScanSettings(requested); if (requested.isInvalid()) return false; - BatchedScanRequest r = new BatchedScanRequest(requested, binder); + BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource); synchronized(mBatchedScanners) { mBatchedScanners.add(r); resolveBatchedScannersLocked(); @@ -468,18 +477,48 @@ public final class WifiService extends IWifiManager.Stub { private void resolveBatchedScannersLocked() { BatchedScanSettings setting = new BatchedScanSettings(); + WorkSource responsibleWorkSource = null; int responsibleUid = 0; + double responsibleCsph = 0; // Channel Scans Per Hour if (mBatchedScanners.size() == 0) { - mWifiStateMachine.setBatchedScanSettings(null, 0); + mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null); return; } for (BatchedScanRequest r : mBatchedScanners) { BatchedScanSettings s = r.settings; + + // evaluate responsibility + int currentChannelCount; + int currentScanInterval; + double currentCsph; + + if (s.channelSet == null || s.channelSet.isEmpty()) { + // all channels - 11 B and 9 A channels roughly. + currentChannelCount = 9 + 11; + } else { + currentChannelCount = s.channelSet.size(); + // these are rough est - no real need to correct for reg-domain; + if (s.channelSet.contains("A")) currentChannelCount += (9 - 1); + if (s.channelSet.contains("B")) currentChannelCount += (11 - 1); + + } + if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) { + currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC; + } else { + currentScanInterval = s.scanIntervalSec; + } + currentCsph = 60 * 60 * currentChannelCount / currentScanInterval; + + if (currentCsph > responsibleCsph) { + responsibleUid = r.uid; + responsibleWorkSource = r.workSource; + responsibleCsph = currentCsph; + } + if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED && s.maxScansPerBatch < setting.maxScansPerBatch) { setting.maxScansPerBatch = s.maxScansPerBatch; - responsibleUid = r.uid; } if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED && (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED || @@ -489,7 +528,6 @@ public final class WifiService extends IWifiManager.Stub { if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED && s.scanIntervalSec < setting.scanIntervalSec) { setting.scanIntervalSec = s.scanIntervalSec; - responsibleUid = r.uid; } if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED && (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED || @@ -511,7 +549,8 @@ public final class WifiService extends IWifiManager.Stub { } setting.constrain(); - mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid); + mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph, + responsibleWorkSource); } private void enforceAccessPermission() { @@ -831,7 +870,7 @@ public final class WifiService extends IWifiManager.Stub { public void setCountryCode(String countryCode, boolean persist) { Slog.i(TAG, "WifiService trying to set country code to " + countryCode + " with persist set to " + persist); - enforceChangePermission(); + enforceConnectivityInternalPermission(); final long token = Binder.clearCallingIdentity(); try { mWifiStateMachine.setCountryCode(countryCode, persist); diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index 8cc1d0211132..e98014bdea3e 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -53,6 +53,7 @@ class AppWindowToken extends WindowToken { int groupId = -1; boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + boolean layoutConfigChanges; boolean showWhenLocked; // The input dispatching timeout for this application token in nanoseconds. diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java index 52f2325d3d33..d358b4c6ed67 100644 --- a/services/java/com/android/server/wm/DisplayContent.java +++ b/services/java/com/android/server/wm/DisplayContent.java @@ -25,9 +25,11 @@ import android.app.ActivityManager.StackBoxInfo; import android.graphics.Rect; import android.graphics.Region; import android.os.Debug; +import android.util.EventLog; import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; +import com.android.server.EventLogTags; import java.io.PrintWriter; import java.util.ArrayList; @@ -97,9 +99,6 @@ class DisplayContent { /** True when the home StackBox is at the top of mStackBoxes, false otherwise. */ private TaskStack mHomeStack = null; - /** Sorted most recent at top, oldest at [0]. */ - ArrayList<TaskStack> mStackHistory = new ArrayList<TaskStack>(); - /** Detect user tapping outside of current focused stack bounds .*/ StackTapPointerEventListener mTapDetector; @@ -107,7 +106,7 @@ class DisplayContent { Region mTouchExcludeRegion = new Region(); /** Save allocating when retrieving tasks */ - ArrayList<Task> mTaskHistory = new ArrayList<Task>(); + private ArrayList<Task> mTaskHistory = new ArrayList<Task>(); /** Save allocating when calculating rects */ Rect mTmpRect = new Rect(); @@ -160,12 +159,6 @@ class DisplayContent { return mStackBoxes.get(0).mStack != mHomeStack; } - void moveStack(TaskStack stack, boolean toTop) { - mStackHistory.remove(stack); - mStackHistory.add(toTop ? mStackHistory.size() : 0, stack); - mService.moveStackWindowsLocked(this); - } - public boolean isPrivate() { return (mDisplay.getFlags() & Display.FLAG_PRIVATE) != 0; } @@ -200,6 +193,7 @@ class DisplayContent { } mTaskHistory.add(taskNdx, task); + EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.taskId, toTop ? 1 : 0, taskNdx); } void removeTask(Task task) { @@ -277,6 +271,8 @@ class DisplayContent { if (newStack != null) { layoutNeeded = true; } + EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId, relativeStackBoxId, position, + (int)(weight * 100 + 0.5)); return newStack; } @@ -345,6 +341,7 @@ class DisplayContent { boolean moveHomeStackBox(boolean toTop) { if (DEBUG_STACK) Slog.d(TAG, "moveHomeStackBox: toTop=" + toTop + " Callers=" + Debug.getCallers(4)); + EventLog.writeEvent(EventLogTags.WM_HOME_STACK_MOVED, toTop ? 1 : 0); switch (mStackBoxes.size()) { case 0: throw new RuntimeException("moveHomeStackBox: No home StackBox!"); case 1: return false; // Only the home StackBox exists. diff --git a/services/java/com/android/server/wm/StackBox.java b/services/java/com/android/server/wm/StackBox.java index d054e9a25ee1..d351925015ee 100644 --- a/services/java/com/android/server/wm/StackBox.java +++ b/services/java/com/android/server/wm/StackBox.java @@ -243,10 +243,6 @@ public class StackBox { /** Remove this box and propagate its sibling's content up to their parent. * @return The first stackId of the resulting StackBox. */ int remove() { - if (mStack != null) { - if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing stackId=" + mStack.mStackId); - mDisplayContent.mStackHistory.remove(mStack); - } mDisplayContent.layoutNeeded = true; if (mParent == null) { diff --git a/services/java/com/android/server/wm/Task.java b/services/java/com/android/server/wm/Task.java index d9acbb95f72f..13fdbc83b2ce 100644 --- a/services/java/com/android/server/wm/Task.java +++ b/services/java/com/android/server/wm/Task.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import android.util.EventLog; +import com.android.server.EventLogTags; + class Task { // private final String TAG = "TaskGroup"; TaskStack mStack; @@ -41,6 +44,8 @@ class Task { boolean removeAppToken(AppWindowToken wtoken) { mAppTokens.remove(wtoken); if (mAppTokens.size() == 0) { + EventLog.writeEvent(com.android.server.EventLogTags.WM_TASK_REMOVED, taskId, + "removeAppToken: last token"); mStack.removeTask(this); return true; } diff --git a/services/java/com/android/server/wm/TaskStack.java b/services/java/com/android/server/wm/TaskStack.java index 34bef68aeb7c..cb29df474874 100644 --- a/services/java/com/android/server/wm/TaskStack.java +++ b/services/java/com/android/server/wm/TaskStack.java @@ -21,8 +21,10 @@ import static com.android.server.wm.WindowManagerService.TAG; import android.graphics.Rect; import android.os.Debug; +import android.util.EventLog; import android.util.Slog; import android.util.TypedValue; +import com.android.server.EventLogTags; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; @@ -45,7 +47,7 @@ public class TaskStack { /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match * mTaskHistory in the ActivityStack with the same mStackId */ - private ArrayList<Task> mTasks = new ArrayList<Task>(); + private final ArrayList<Task> mTasks = new ArrayList<Task>(); /** The StackBox this sits in. */ StackBox mStackBox; @@ -70,7 +72,6 @@ public class TaskStack { mService = service; mStackId = stackId; mDisplayContent = displayContent; - final int displayId = displayContent.getDisplayId(); mDimLayer = new DimLayer(service, this); mAnimationBackgroundSurface = new DimLayer(service, this); } @@ -152,6 +153,7 @@ public class TaskStack { int remove() { mAnimationBackgroundSurface.destroySurface(); mDimLayer.destroySurface(); + EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId); return mStackBox.remove(); } @@ -269,6 +271,8 @@ public class TaskStack { for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { final WindowState win = windows.get(winNdx); if (!resizingWindows.contains(win)) { + if (WindowManagerService.DEBUG_RESIZE) Slog.d(TAG, + "setBounds: Resizing " + win); resizingWindows.add(win); } win.mUnderStatusBar = underStatusBar; diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index cd46bb8940d2..91f15f3fb7f1 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -245,7 +245,7 @@ public class WindowAnimator { mForceHiding = KEYGUARD_ANIMATING_OUT; } } else { - mForceHiding = KEYGUARD_SHOWN; + mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN; } } if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 63e09db3ec38..64bbe49342d9 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -437,8 +437,15 @@ public class WindowManagerService extends IWindowManager.Stub int mRotation = 0; int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; boolean mAltOrientation = false; - ArrayList<IRotationWatcher> mRotationWatchers - = new ArrayList<IRotationWatcher>(); + class RotationWatcher { + IRotationWatcher watcher; + IBinder.DeathRecipient dr; + RotationWatcher(IRotationWatcher w, IBinder.DeathRecipient d) { + watcher = w; + dr = d; + } + } + ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<RotationWatcher>(); int mDeferredRotationPauseCount; int mSystemDecorLayer = 0; @@ -578,10 +585,13 @@ public class WindowManagerService extends IWindowManager.Stub private boolean mUpdateRotation = false; boolean mWallpaperActionPending = false; - private static final int DISPLAY_CONTENT_UNKNOWN = 0; - private static final int DISPLAY_CONTENT_MIRROR = 1; - private static final int DISPLAY_CONTENT_UNIQUE = 2; - private int mDisplayHasContent = DISPLAY_CONTENT_UNKNOWN; + // Set to true when the display contains content to show the user. + // When false, the display manager may choose to mirror or blank the display. + boolean mDisplayHasContent = false; + + // Only set while traversing the default display based on its content. + // Affects the behavior of mirroring on secondary displays. + boolean mObscureApplicationContentOnSecondaryDisplays = false; } final LayoutFields mInnerFields = new LayoutFields(); @@ -3394,16 +3404,17 @@ public class WindowManagerService extends IWindowManager.Stub if (stack == null) { throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId); } + EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId); Task task = new Task(atoken, stack, userId); mTaskIdToTask.put(taskId, task); stack.addTask(task, true); - stack.getDisplayContent().moveStack(stack, true); return task; } @Override public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId, - int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId) { + int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, + int configChanges) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); @@ -3435,6 +3446,8 @@ public class WindowManagerService extends IWindowManager.Stub atoken.appFullscreen = fullscreen; atoken.showWhenLocked = showWhenLocked; atoken.requestedOrientation = requestedOrientation; + atoken.layoutConfigChanges = (configChanges & + (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0; if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken + " to stack=" + stackId + " task=" + taskId + " at " + addPos); @@ -4303,10 +4316,6 @@ public class WindowManagerService extends IWindowManager.Stub // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. if (okToDisplay() && mAppTransition.isTransitionSet()) { - // Already in requested state, don't do anything more. - if (wtoken.hiddenRequested != visible) { - return; - } wtoken.hiddenRequested = !visible; if (!wtoken.startingDisplayed) { @@ -4792,7 +4801,6 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.moveHomeStackBox(isHomeStackTask); } stack.moveTaskToTop(task); - displayContent.moveStack(stack, true); } } finally { Binder.restoreCallingIdentity(origId); @@ -4846,7 +4854,6 @@ public class WindowManagerService extends IWindowManager.Stub weight); if (stack != null) { mStackIdToStack.put(stackId, stack); - displayContent.moveStack(stack, true); performLayoutAndPlaceSurfacesLocked(); return; } @@ -4878,6 +4885,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } final TaskStack stack = task.mStack; + EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, taskId, "removeTask"); stack.removeTask(task); stack.getDisplayContent().layoutNeeded = true; } @@ -5992,7 +6000,7 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=mRotationWatchers.size()-1; i>=0; i--) { try { - mRotationWatchers.get(i).onRotationChanged(rotation); + mRotationWatchers.get(i).watcher.onRotationChanged(rotation); } catch (RemoteException e) { } } @@ -6024,10 +6032,10 @@ public class WindowManagerService extends IWindowManager.Stub public void binderDied() { synchronized (mWindowMap) { for (int i=0; i<mRotationWatchers.size(); i++) { - if (watcherBinder == mRotationWatchers.get(i).asBinder()) { - IRotationWatcher removed = mRotationWatchers.remove(i); + if (watcherBinder == mRotationWatchers.get(i).watcher.asBinder()) { + RotationWatcher removed = mRotationWatchers.remove(i); if (removed != null) { - removed.asBinder().unlinkToDeath(this, 0); + removed.watcher.asBinder().unlinkToDeath(this, 0); } i--; } @@ -6039,7 +6047,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { try { watcher.asBinder().linkToDeath(dr, 0); - mRotationWatchers.add(watcher); + mRotationWatchers.add(new RotationWatcher(watcher, dr)); } catch (RemoteException e) { // Client died, no cleanup needed. } @@ -6053,9 +6061,13 @@ public class WindowManagerService extends IWindowManager.Stub final IBinder watcherBinder = watcher.asBinder(); synchronized (mWindowMap) { for (int i=0; i<mRotationWatchers.size(); i++) { - if (watcherBinder == mRotationWatchers.get(i).asBinder()) { - mRotationWatchers.remove(i); - i--; + RotationWatcher rotationWatcher = mRotationWatchers.get(i); + if (watcherBinder == rotationWatcher.watcher.asBinder()) { + RotationWatcher removed = mRotationWatchers.remove(i); + if (removed != null) { + removed.watcher.asBinder().unlinkToDeath(removed.dr, 0); + i--; + } } } } @@ -8269,7 +8281,9 @@ public class WindowManagerService extends IWindowManager.Stub // windows, since that means "perform layout as normal, // just don't display"). if (!gone || !win.mHaveFrame || win.mLayoutNeeded - || (win.mAttrs.type == TYPE_KEYGUARD && win.isConfigChanged()) + || ((win.isConfigChanged() || win.setInsetsChanged()) && + (win.mAttrs.type == TYPE_KEYGUARD || + win.mAppToken != null && win.mAppToken.layoutConfigChanges)) || win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) { if (!win.mLayoutAttached) { if (initial) { @@ -8703,12 +8717,7 @@ public class WindowManagerService extends IWindowManager.Stub private void updateResizingWindows(final WindowState w) { final WindowStateAnimator winAnimator = w.mWinAnimator; if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq) { - w.mOverscanInsetsChanged |= - !w.mLastOverscanInsets.equals(w.mOverscanInsets); - w.mContentInsetsChanged |= - !w.mLastContentInsets.equals(w.mContentInsets); - w.mVisibleInsetsChanged |= - !w.mLastVisibleInsets.equals(w.mVisibleInsets); + w.setInsetsChanged(); boolean configChanged = w.isConfigChanged(); if (DEBUG_CONFIGURATION && configChanged) { Slog.v(TAG, "Win " + w + " config changed: " @@ -8783,6 +8792,14 @@ public class WindowManagerService extends IWindowManager.Stub final WindowManager.LayoutParams attrs = w.mAttrs; final int attrFlags = attrs.flags; final boolean canBeSeen = w.isDisplayedLw(); + final boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn(); + + if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) { + // This window completely covers everything behind it, + // so we want to leave all of them as undimmed (for + // performance reasons). + mInnerFields.mObscured = true; + } if (w.mHasSurface) { if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) { @@ -8811,22 +8828,24 @@ public class WindowManagerService extends IWindowManager.Stub } if (canBeSeen) { - if (type == TYPE_DREAM || type == TYPE_KEYGUARD) { - mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_MIRROR; - } else if (mInnerFields.mDisplayHasContent - == LayoutFields.DISPLAY_CONTENT_UNKNOWN) { - mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNIQUE; + // This function assumes that the contents of the default display are + // processed first before secondary displays. + if (w.mDisplayContent.isDefaultDisplay) { + // While a dream or keyguard is showing, obscure ordinary application + // content on secondary displays (by forcibly enabling mirroring unless + // there is other content we want to show) but still allow opaque + // keyguard dialogs to be shown. + if (type == TYPE_DREAM || type == TYPE_KEYGUARD) { + mInnerFields.mObscureApplicationContentOnSecondaryDisplays = true; + } + mInnerFields.mDisplayHasContent = true; + } else if (!mInnerFields.mObscureApplicationContentOnSecondaryDisplays + || (mInnerFields.mObscured && type == TYPE_KEYGUARD_DIALOG)) { + // Allow full screen keyguard presentation dialogs to be seen. + mInnerFields.mDisplayHasContent = true; } } } - - boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn(); - if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) { - // This window completely covers everything behind it, - // so we want to leave all of them as undimmed (for - // performance reasons). - mInnerFields.mObscured = true; - } } private void handleFlagDimBehind(WindowState w, int innerDw, int innerDh) { @@ -8904,7 +8923,7 @@ public class WindowManagerService extends IWindowManager.Stub mInnerFields.mScreenBrightness = -1; mInnerFields.mButtonBrightness = -1; mInnerFields.mUserActivityTimeout = -1; - mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN; + mInnerFields.mObscureApplicationContentOnSecondaryDisplays = false; mTransactionSequence++; @@ -8939,10 +8958,8 @@ public class WindowManagerService extends IWindowManager.Stub final int innerDh = displayInfo.appHeight; final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); - // Reset for each display unless we are forcing mirroring. - if (mInnerFields.mDisplayHasContent != LayoutFields.DISPLAY_CONTENT_MIRROR) { - mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN; - } + // Reset for each display. + mInnerFields.mDisplayHasContent = false; int repeats = 0; do { @@ -9151,20 +9168,8 @@ public class WindowManagerService extends IWindowManager.Stub updateResizingWindows(w); } - final boolean hasUniqueContent; - switch (mInnerFields.mDisplayHasContent) { - case LayoutFields.DISPLAY_CONTENT_MIRROR: - hasUniqueContent = isDefaultDisplay; - break; - case LayoutFields.DISPLAY_CONTENT_UNIQUE: - hasUniqueContent = true; - break; - case LayoutFields.DISPLAY_CONTENT_UNKNOWN: - default: - hasUniqueContent = false; - break; - } - mDisplayManagerService.setDisplayHasContent(displayId, hasUniqueContent, + mDisplayManagerService.setDisplayHasContent(displayId, + mInnerFields.mDisplayHasContent, true /* inTraversal, must call performTraversalInTrans... below */); getDisplayContentLocked(displayId).stopDimmingIfNeeded(); diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 2d087925e56b..4d53cea4370d 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -701,6 +701,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mAppToken != null ? mAppToken.appToken : null; } + boolean setInsetsChanged() { + mOverscanInsetsChanged |= !mLastOverscanInsets.equals(mOverscanInsets); + mContentInsetsChanged |= !mLastContentInsets.equals(mContentInsets); + mVisibleInsetsChanged |= !mLastVisibleInsets.equals(mVisibleInsets); + return mOverscanInsetsChanged || mContentInsetsChanged || mVisibleInsetsChanged; + } + public int getDisplayId() { return mDisplayContent.getDisplayId(); } diff --git a/services/jni/com_android_server_connectivity_Vpn.cpp b/services/jni/com_android_server_connectivity_Vpn.cpp index ab8c959f692f..bf34a748061d 100644 --- a/services/jni/com_android_server_connectivity_Vpn.cpp +++ b/services/jni/com_android_server_connectivity_Vpn.cpp @@ -302,15 +302,6 @@ static int check_interface(const char *name) return ifr4.ifr_flags; } -static int bind_to_interface(int socket, const char *name) -{ - if (setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) { - ALOGE("Cannot bind socket to %s: %s", name, strerror(errno)); - return SYSTEM_ERROR; - } - return 0; -} - //------------------------------------------------------------------------------ static void throwException(JNIEnv *env, int error, const char *message) @@ -433,19 +424,6 @@ static jint check(JNIEnv *env, jobject thiz, jstring jName) return flags; } -static void protect(JNIEnv *env, jobject thiz, jint socket, jstring jName) -{ - const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; - if (!name) { - jniThrowNullPointerException(env, "name"); - return; - } - if (bind_to_interface(socket, name) < 0) { - throwException(env, SYSTEM_ERROR, "Cannot protect socket"); - } - env->ReleaseStringUTFChars(jName, name); -} - //------------------------------------------------------------------------------ static JNINativeMethod gMethods[] = { @@ -455,7 +433,6 @@ static JNINativeMethod gMethods[] = { {"jniSetRoutes", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setRoutes}, {"jniReset", "(Ljava/lang/String;)V", (void *)reset}, {"jniCheck", "(Ljava/lang/String;)I", (void *)check}, - {"jniProtect", "(ILjava/lang/String;)V", (void *)protect}, }; int register_android_server_connectivity_Vpn(JNIEnv *env) diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java index 37176d6d31f4..910b6855dc21 100644 --- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java +++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java @@ -136,10 +136,11 @@ public class SyncOperationTest extends AndroidTestCase { "authority1", b1, soon + 100, soonFlex + 100, unimportant, unimportant, true); assertTrue(op1.compareTo(op2) == -1); - assertTrue("less than not transitive.", op2.compareTo(op1) == 1); - assertTrue(op1.compareTo(op3) == 1); - assertTrue("greater than not transitive. ", op3.compareTo(op1) == -1); - assertTrue("overlapping intervals not the same.", op1.compareTo(op4) == 0); - assertTrue("equality not transitive.", op4.compareTo(op1) == 0); + assertTrue("Less than not transitive.", op2.compareTo(op1) == 1); + assertEquals("Expedited not smaller than non-expedited.", -1, op1.compareTo(op3)); + assertEquals("Greater than not transitive for expedited. ", 1, op3.compareTo(op1)); + assertTrue("overlapping intervals not compared based on start interval.", + op1.compareTo(op4) == -1); + assertTrue("overlapping interval comparison not transitive.", op4.compareTo(op1) == 1); } } diff --git a/telephony/java/android/telephony/CallStateListener.java b/telephony/java/android/telephony/CallStateListener.java new file mode 100644 index 000000000000..e2ffbfa55be0 --- /dev/null +++ b/telephony/java/android/telephony/CallStateListener.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.PrivateApi; + +/** @hide */ +@PrivateApi +public interface CallStateListener { + /** + * Notify of a new or updated call. + * Any time the state of a call is updated, it will alert any listeners. This includes changes + * of state such as when a call is put on hold or conferenced. + * + * @param callId a unique ideCntifier for a given call that can be used to track state changes + * @param state the new state of the call. + * {@see com.android.services.telephony.common.Call$State} + * @param number the phone number of the call. For some states, this may be blank. However, it + * will be populated for any initial state. + */ + public void onCallStateChanged(int callId, int state, String number); +} diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index ab429fd4b7ae..e3a1aa697cd2 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -1957,6 +1957,27 @@ public class PhoneNumberUtils } /** + * Process phone number for CDMA, converting plus code using the home network number format. + * This is used for outgoing SMS messages. + * + * @param dialStr the original dial string + * @return the converted dial string + * @hide for internal use + */ + public static String cdmaCheckAndProcessPlusCodeForSms(String dialStr) { + if (!TextUtils.isEmpty(dialStr)) { + if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) { + String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); + if (!TextUtils.isEmpty(defaultIso)) { + int format = getFormatTypeFromCountryCode(defaultIso); + return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, format, format); + } + } + } + return dialStr; + } + + /** * This function should be called from checkAndProcessPlusCode only * And it is used for test purpose also. * diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 8f17e7279924..3ab7000fec1b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -16,17 +16,22 @@ package android.telephony; +import android.annotation.PrivateApi; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.telephony.Rlog; +import android.util.Log; import com.android.internal.telephony.IPhoneSubInfo; import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.ITelephonyListener; import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.RILConstants; @@ -34,6 +39,7 @@ import com.android.internal.telephony.TelephonyProperties; import java.io.FileInputStream; import java.io.IOException; +import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -60,8 +66,41 @@ public class TelephonyManager { private static final String TAG = "TelephonyManager"; private static ITelephonyRegistry sRegistry; + + private final HashMap<CallStateListener,Listener> mListeners + = new HashMap<CallStateListener,Listener>(); private final Context mContext; + private static class Listener extends ITelephonyListener.Stub { + final CallStateListener mListener; + private static final int WHAT = 1; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + mListener.onCallStateChanged(msg.arg1, msg.arg2, (String)msg.obj); + } + }; + + Listener(CallStateListener listener) { + mListener = listener; + } + + @Override + public void onUpdate(final int callId, final int state, final String number) { + if (mHandler != null) { + mHandler.sendMessage(mHandler.obtainMessage(WHAT, callId, state, number)); + } + } + + void clearQueue() { + mHandler.removeMessages(WHAT); + + // Don't accept more incoming binder calls either. + mHandler = null; + } + } + /** @hide */ public TelephonyManager(Context context) { Context appContext = context.getApplicationContext(); @@ -1439,4 +1478,397 @@ public class TelephonyManager { return mContext.getResources().getString( com.android.internal.R.string.config_mms_user_agent_profile_url); } + + /** @hide */ + @PrivateApi + public void dial(String number) { + try { + getITelephony().dial(number); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#dial", e); + } + } + + /** @hide */ + @PrivateApi + public void call(String callingPackage, String number) { + try { + getITelephony().call(callingPackage, number); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#call", e); + } + } + + /** @hide */ + @PrivateApi + public boolean showCallScreen() { + try { + return getITelephony().showCallScreen(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#showCallScreen", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean showCallScreenWithDialpad(boolean showDialpad) { + try { + return getITelephony().showCallScreenWithDialpad(showDialpad); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#showCallScreenWithDialpad", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean endCall() { + try { + return getITelephony().endCall(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#endCall", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public void answerRingingCall() { + try { + getITelephony().answerRingingCall(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#answerRingingCall", e); + } + } + + /** @hide */ + @PrivateApi + public void toggleHold() { + try { + getITelephony().toggleHold(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#toggleHold", e); + } + } + + /** @hide */ + @PrivateApi + public void merge() { + try { + getITelephony().merge(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#merge", e); + } + } + + /** @hide */ + @PrivateApi + public void swap() { + try { + getITelephony().swap(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#swap", e); + } + } + + /** @hide */ + @PrivateApi + public void mute(boolean mute) { + try { + getITelephony().mute(mute); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#mute", e); + } + } + + /** @hide */ + @PrivateApi + public void silenceRinger() { + try { + getITelephony().silenceRinger(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#silenceRinger", e); + } + } + + /** @hide */ + @PrivateApi + public boolean isOffhook() { + try { + return getITelephony().isOffhook(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isOffhook", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean isRinging() { + try { + return getITelephony().isRinging(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isRinging", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean isIdle() { + try { + return getITelephony().isIdle(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isIdle", e); + } + return true; + } + + /** @hide */ + @PrivateApi + public boolean isRadioOn() { + try { + return getITelephony().isRadioOn(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isRadioOn", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean isSimPinEnabled() { + try { + return getITelephony().isSimPinEnabled(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isSimPinEnabled", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public void cancelMissedCallsNotification() { + try { + getITelephony().cancelMissedCallsNotification(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#cancelMissedCallsNotification", e); + } + } + + /** @hide */ + @PrivateApi + public boolean supplyPin(String pin) { + try { + return getITelephony().supplyPin(pin); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#supplyPin", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean supplyPuk(String puk, String pin) { + try { + return getITelephony().supplyPuk(puk, pin); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#supplyPuk", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public int[] supplyPinReportResult(String pin) { + try { + return getITelephony().supplyPinReportResult(pin); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#supplyPinReportResult", e); + } + return new int[0]; + } + + /** @hide */ + @PrivateApi + public int[] supplyPukReportResult(String puk, String pin) { + try { + return getITelephony().supplyPukReportResult(puk, pin); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#]", e); + } + return new int[0]; + } + + /** @hide */ + @PrivateApi + public boolean handlePinMmi(String dialString) { + try { + return getITelephony().handlePinMmi(dialString); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#handlePinMmi", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public void toggleRadioOnOff() { + try { + getITelephony().toggleRadioOnOff(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#toggleRadioOnOff", e); + } + } + + /** @hide */ + @PrivateApi + public boolean setRadio(boolean turnOn) { + try { + return getITelephony().setRadio(turnOn); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setRadio", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean setRadioPower(boolean turnOn) { + try { + return getITelephony().setRadioPower(turnOn); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setRadioPower", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public void updateServiceLocation() { + try { + getITelephony().updateServiceLocation(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#updateServiceLocation", e); + } + } + + /** @hide */ + @PrivateApi + public int enableApnType(String type) { + try { + return getITelephony().enableApnType(type); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#enableApnType", e); + } + return PhoneConstants.APN_REQUEST_FAILED; + } + + /** @hide */ + @PrivateApi + public int disableApnType(String type) { + try { + return getITelephony().disableApnType(type); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#disableApnType", e); + } + return PhoneConstants.APN_REQUEST_FAILED; + } + + /** @hide */ + @PrivateApi + public boolean enableDataConnectivity() { + try { + return getITelephony().enableDataConnectivity(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#enableDataConnectivity", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean disableDataConnectivity() { + try { + return getITelephony().disableDataConnectivity(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#disableDataConnectivity", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean isDataConnectivityPossible() { + try { + return getITelephony().isDataConnectivityPossible(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isDataConnectivityPossible", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public boolean needsOtaServiceProvisioning() { + try { + return getITelephony().needsOtaServiceProvisioning(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#needsOtaServiceProvisioning", e); + } + return false; + } + + /** @hide */ + @PrivateApi + public void playDtmfTone(char digit, boolean timedShortCode) { + try { + getITelephony().playDtmfTone(digit, timedShortCode); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#playDtmfTone", e); + } + } + + /** @hide */ + @PrivateApi + public void stopDtmfTone() { + try { + getITelephony().stopDtmfTone(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#stopDtmfTone", e); + } + } + + /** @hide */ + @PrivateApi + public void addCallStateListener(CallStateListener listener) { + try { + if (listener == null) { + throw new RuntimeException("Listener can't be null"); + } + if (!mListeners.containsKey(listener)) { + final Listener l = new Listener(listener); + mListeners.put(listener, l); + getITelephony().addListener(l); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#addListener", e); + } + } + + /** @hide */ + @PrivateApi + public void removeCallStateListener(CallStateListener listener) { + try { + final Listener l = mListeners.remove(listener); + if (l != null) { + // Make sure that no callbacks that are already in flight come. + l.clearQueue(); + getITelephony().removeListener(l); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#removeListener", e); + } + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 7bd2c8412e97..9c73059f7a7c 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -17,9 +17,12 @@ package com.android.internal.telephony; import android.os.Bundle; -import java.util.List; -import android.telephony.NeighboringCellInfo; import android.telephony.CellInfo; +import android.telephony.NeighboringCellInfo; + +import com.android.internal.telephony.ITelephonyListener; + +import java.util.List; /** * Interface used to interact with the phone. Mostly this is used by the @@ -324,5 +327,48 @@ interface ITelephony { * Sets minimum time in milli-seconds between onCellInfoChanged */ void setCellInfoListRate(int rateInMillis); + + /** + * Put a call on hold. + */ + void toggleHold(); + + /** + * Merge foreground and background calls. + */ + void merge(); + + /** + * Swap foreground and background calls. + */ + void swap(); + + /** + * Mute the phone. + */ + void mute(boolean mute); + + /** + * Start playing DTMF tone for the specified digit. + * + * @param digit the digit that corresponds with the desired tone. + * @param timedShortcode whether the specified digit should be played as a timed short code. + */ + void playDtmfTone(char digit, boolean timedShortCode); + + /** + * Stop playing DTMF tones. + */ + void stopDtmfTone(); + + /** + * Register a callback. + */ + void addListener(ITelephonyListener listener); + + /** + * Unregister a callback. + */ + void removeListener(ITelephonyListener listener); } diff --git a/telephony/java/com/android/internal/telephony/ITelephonyListener.aidl b/telephony/java/com/android/internal/telephony/ITelephonyListener.aidl new file mode 100644 index 000000000000..c2262172a3c7 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/ITelephonyListener.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +/** + * Interface used to register a listener that gets more detailed call state information than + * {@link android.telephony.PhoneStateListener} + * + * {@hide} + */ +oneway interface ITelephonyListener { + void onUpdate(int callId, int state, String number); +} diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index 62f6affd9b3a..dfb80707f14c 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -85,11 +85,13 @@ public class AppLaunch extends InstrumentationTestCase { // do initial app launch, without force stopping for (String app : mNameToResultKey.keySet()) { long launchTime = startApp(app, false); - if (launchTime <=0 ) { + if (launchTime <= 0) { mNameToLaunchTime.put(app, -1L); // simply pass the app if launch isn't successful // error should have already been logged by startApp continue; + } else { + mNameToLaunchTime.put(app, launchTime); } sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); closeApp(app, false); @@ -98,9 +100,9 @@ public class AppLaunch extends InstrumentationTestCase { // do the real app launch now for (int i = 0; i < mLaunchIterations; i++) { for (String app : mNameToResultKey.keySet()) { - long totalLaunchTime = mNameToLaunchTime.get(app); + long prevLaunchTime = mNameToLaunchTime.get(app); long launchTime = 0; - if (totalLaunchTime < 0) { + if (prevLaunchTime < 0) { // skip if the app has previous failures continue; } @@ -110,18 +112,19 @@ public class AppLaunch extends InstrumentationTestCase { mNameToLaunchTime.put(app, -1L); continue; } - totalLaunchTime += launchTime; - mNameToLaunchTime.put(app, totalLaunchTime); + // keep the min launch time + if (launchTime < prevLaunchTime) { + mNameToLaunchTime.put(app, launchTime); + } sleep(POST_LAUNCH_IDLE_TIMEOUT); closeApp(app, true); sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); } } for (String app : mNameToResultKey.keySet()) { - long totalLaunchTime = mNameToLaunchTime.get(app); - if (totalLaunchTime != -1) { - mResult.putDouble(mNameToResultKey.get(app), - ((double) totalLaunchTime) / mLaunchIterations); + long launchTime = mNameToLaunchTime.get(app); + if (launchTime != -1) { + mResult.putLong(mNameToResultKey.get(app), launchTime); } } instrumentation.sendStatus(0, mResult); diff --git a/tests/LegacyRestoreTest/README b/tests/LegacyRestoreTest/README new file mode 100644 index 000000000000..cdd157e58295 --- /dev/null +++ b/tests/LegacyRestoreTest/README @@ -0,0 +1,18 @@ +The file "jbmr2-encrypted-settings-abcd.ab" in this directory is an encrypted +"adb backup" archive of the settings provider package. It was generated on a +Nexus 4 running Android 4.3 (API 18), and so predates the Android 4.4 changes +to the PBKDF2 implementation. The archive's encryption password, entered on-screen, +is "abcd" (with no quotation marks). + +'adb restore' decrypts and applies the restored archive successfully on a device +running Android 4.3, but fails to restore correctly on a device running Android 4.4, +reporting an invalid password in logcat. This is the situation reported in bug +<https://code.google.com/p/android/issues/detail?id=63880>. + +The file "kk-fixed-encrypted-settings-abcd.ab" is a similar encrypted "adb backup" +archive, using the same key, generated on a Nexus 4 running Android 4.4 with a fix +to this bug in place. This archive should be successfully restorable on any +version of Android which incorporates the fix. + +These archives can be used as an ongoing test to verify that historical encrypted +archives from various points in Android's history can be successfully restored. diff --git a/tests/LegacyRestoreTest/jbmr2-encrypted-settings-abcd.ab b/tests/LegacyRestoreTest/jbmr2-encrypted-settings-abcd.ab Binary files differnew file mode 100644 index 000000000000..192dcf5d4c52 --- /dev/null +++ b/tests/LegacyRestoreTest/jbmr2-encrypted-settings-abcd.ab diff --git a/tests/LegacyRestoreTest/kk-fixed-encrypted-settings-abcd.ab b/tests/LegacyRestoreTest/kk-fixed-encrypted-settings-abcd.ab Binary files differnew file mode 100644 index 000000000000..bf2b558d49b9 --- /dev/null +++ b/tests/LegacyRestoreTest/kk-fixed-encrypted-settings-abcd.ab diff --git a/tests/RemoteDisplayProvider/Android.mk b/tests/RemoteDisplayProvider/Android.mk new file mode 100644 index 000000000000..2f4b34324c31 --- /dev/null +++ b/tests/RemoteDisplayProvider/Android.mk @@ -0,0 +1,26 @@ +# Copyright (C) 2013 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. + +LOCAL_PATH := $(call my-dir) + +# Build the application. +include $(CLEAR_VARS) +LOCAL_PACKAGE_NAME := RemoteDisplayProviderTest +LOCAL_MODULE_TAGS := tests +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res +LOCAL_JAVA_LIBRARIES := com.android.media.remotedisplay +LOCAL_CERTIFICATE := platform +include $(BUILD_PACKAGE) diff --git a/tests/RemoteDisplayProvider/AndroidManifest.xml b/tests/RemoteDisplayProvider/AndroidManifest.xml new file mode 100644 index 000000000000..afb7c78c3891 --- /dev/null +++ b/tests/RemoteDisplayProvider/AndroidManifest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.media.remotedisplay.test" > + + <uses-sdk android:minSdkVersion="19" /> + <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/> + + <application android:label="@string/app_name" + android:icon="@drawable/ic_app"> + <uses-library android:name="com.android.media.remotedisplay" + android:required="true" /> + + <service android:name=".RemoteDisplayProviderService" + android:label="@string/app_name" + android:exported="true" + android:permission="android.permission.BIND_REMOTE_DISPLAY"> + <intent-filter> + <action android:name="com.android.media.remotedisplay.RemoteDisplayProvider"/> + </intent-filter> + </service> + + </application> +</manifest> diff --git a/tests/RemoteDisplayProvider/README b/tests/RemoteDisplayProvider/README new file mode 100644 index 000000000000..8bf0130bd800 --- /dev/null +++ b/tests/RemoteDisplayProvider/README @@ -0,0 +1,16 @@ +This directory contains sample code to test system integration with +remote display providers using the API declared by the +com.android.media.remotedisplay.jar library. + +--- DESCRIPTION --- + +The application registers a service that publishes a few different +remote display routes. Behavior can be controlled by modifying the +code. + +To exercise the provider, use System UI features for connecting to +wireless displays or launch an activity that uses the MediaRouter, +such as the PresentationWithMediaRouterActivity in ApiDemos. + +This code is mainly intended for development and not meant to be +used as an example implementation of a robust remote display provider. diff --git a/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png b/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png Binary files differnew file mode 100755 index 000000000000..66a198496cfb --- /dev/null +++ b/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png diff --git a/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png b/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png Binary files differnew file mode 100644 index 000000000000..5ae7701a8a85 --- /dev/null +++ b/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png diff --git a/core/res/res/values-ja/bools.xml b/tests/RemoteDisplayProvider/res/values/strings.xml index 59cf744532eb..dd82d2cbcdd2 100644 --- a/core/res/res/values-ja/bools.xml +++ b/tests/RemoteDisplayProvider/res/values/strings.xml @@ -14,7 +14,6 @@ limitations under the License. --> -<resources> - <bool name="flip_controller_fallback_keys">true</bool> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_name">Remote Display Provider Test</string> </resources> - diff --git a/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java b/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java new file mode 100644 index 000000000000..611d7e4e6407 --- /dev/null +++ b/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.media.remotedisplay.test; + +import com.android.media.remotedisplay.RemoteDisplay; +import com.android.media.remotedisplay.RemoteDisplayProvider; + +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; + +/** + * Remote display provider implementation that publishes working routes. + */ +public class RemoteDisplayProviderService extends Service { + private static final String TAG = "RemoteDisplayProviderTest"; + + private Provider mProvider; + + @Override + public IBinder onBind(Intent intent) { + if (intent.getAction().equals(RemoteDisplayProvider.SERVICE_INTERFACE)) { + if (mProvider == null) { + mProvider = new Provider(); + return mProvider.getBinder(); + } + } + return null; + } + + final class Provider extends RemoteDisplayProvider { + private RemoteDisplay mTestDisplay1; // variable volume + private RemoteDisplay mTestDisplay2; // fixed volume + private RemoteDisplay mTestDisplay3; // not available + private RemoteDisplay mTestDisplay4; // in use + private RemoteDisplay mTestDisplay5; // available but ignores request to connect + private RemoteDisplay mTestDisplay6; // available but never finishes connecting + private RemoteDisplay mTestDisplay7; // blinks in and out of existence + private RemoteDisplay mTestDisplay8; // available but connecting attempt flakes out + private RemoteDisplay mTestDisplay9; // available but connection flakes out + private RemoteDisplay mTestDisplay10; // available and reconnects periodically + + private final Handler mHandler; + private boolean mBlinking; + + public Provider() { + super(RemoteDisplayProviderService.this); + mHandler = new Handler(getMainLooper()); + } + + @Override + public void onDiscoveryModeChanged(int mode) { + Log.d(TAG, "onDiscoveryModeChanged: mode=" + mode); + + if (mode != DISCOVERY_MODE_NONE) { + // When discovery begins, go find all of the routes. + if (mTestDisplay1 == null) { + mTestDisplay1 = new RemoteDisplay("testDisplay1", + "Test Display 1 (variable)"); + mTestDisplay1.setDescription("Variable volume"); + mTestDisplay1.setStatus(RemoteDisplay.STATUS_AVAILABLE); + mTestDisplay1.setVolume(10); + mTestDisplay1.setVolumeHandling(RemoteDisplay.PLAYBACK_VOLUME_VARIABLE); + mTestDisplay1.setVolumeMax(15); + addDisplay(mTestDisplay1); + } + if (mTestDisplay2 == null) { + mTestDisplay2 = new RemoteDisplay("testDisplay2", + "Test Display 2 (fixed)"); + mTestDisplay2.setDescription("Fixed volume"); + mTestDisplay2.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay2); + } + if (mTestDisplay3 == null) { + mTestDisplay3 = new RemoteDisplay("testDisplay3", + "Test Display 3 (unavailable)"); + mTestDisplay3.setDescription("Always unavailable"); + mTestDisplay3.setStatus(RemoteDisplay.STATUS_NOT_AVAILABLE); + addDisplay(mTestDisplay3); + } + if (mTestDisplay4 == null) { + mTestDisplay4 = new RemoteDisplay("testDisplay4", + "Test Display 4 (in-use)"); + mTestDisplay4.setDescription("Always in-use"); + mTestDisplay4.setStatus(RemoteDisplay.STATUS_IN_USE); + addDisplay(mTestDisplay4); + } + if (mTestDisplay5 == null) { + mTestDisplay5 = new RemoteDisplay("testDisplay5", + "Test Display 5 (connect ignored)"); + mTestDisplay5.setDescription("Ignores connect"); + mTestDisplay5.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay5); + } + if (mTestDisplay6 == null) { + mTestDisplay6 = new RemoteDisplay("testDisplay6", + "Test Display 6 (connect hangs)"); + mTestDisplay6.setDescription("Never finishes connecting"); + mTestDisplay6.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay6); + } + if (mTestDisplay8 == null) { + mTestDisplay8 = new RemoteDisplay("testDisplay8", + "Test Display 8 (flaky when connecting)"); + mTestDisplay8.setDescription("Aborts spontaneously while connecting"); + mTestDisplay8.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay8); + } + if (mTestDisplay9 == null) { + mTestDisplay9 = new RemoteDisplay("testDisplay9", + "Test Display 9 (flaky when connected)"); + mTestDisplay9.setDescription("Aborts spontaneously while connected"); + mTestDisplay9.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay9); + } + if (mTestDisplay10 == null) { + mTestDisplay10 = new RemoteDisplay("testDisplay10", + "Test Display 10 (reconnects periodically)"); + mTestDisplay10.setDescription("Reconnects spontaneously"); + mTestDisplay10.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay10); + } + } else { + // When discovery ends, go hide some of the routes we can't actually use. + // This isn't something a normal route provider would do though. + // The routes will usually stay published. + if (mTestDisplay3 != null) { + removeDisplay(mTestDisplay3); + mTestDisplay3 = null; + } + if (mTestDisplay4 != null) { + removeDisplay(mTestDisplay4); + mTestDisplay4 = null; + } + } + + // When active discovery is on, pretend there's a route that we can't quite + // reach that blinks in and out of existence. + if (mode == DISCOVERY_MODE_ACTIVE) { + if (!mBlinking) { + mBlinking = true; + mHandler.post(mBlink); + } + } else { + mBlinking = false; + } + } + + @Override + public void onConnect(final RemoteDisplay display) { + Log.d(TAG, "onConnect: display.getId()=" + display.getId()); + + if (display == mTestDisplay1 || display == mTestDisplay2) { + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay1 || display == mTestDisplay2) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_CONNECTED); + updateDisplay(display); + } + } + }, 2000); + } else if (display == mTestDisplay6 || display == mTestDisplay7) { + // never finishes connecting + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + } else if (display == mTestDisplay8) { + // flakes out while connecting + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay8) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_AVAILABLE); + updateDisplay(display); + } + } + }, 2000); + } else if (display == mTestDisplay9) { + // flakes out when connected + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay9) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_CONNECTED); + updateDisplay(display); + } + } + }, 2000); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay9) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTED) { + display.setStatus(RemoteDisplay.STATUS_AVAILABLE); + updateDisplay(display); + } + } + }, 5000); + } else if (display == mTestDisplay10) { + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (display == mTestDisplay10) { + if (display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_CONNECTED); + updateDisplay(display); + mHandler.postDelayed(this, 7000); + } else if (display.getStatus() == RemoteDisplay.STATUS_CONNECTED) { + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(this, 2000); + } + } + } + }, 2000); + } + } + + @Override + public void onDisconnect(RemoteDisplay display) { + Log.d(TAG, "onDisconnect: display.getId()=" + display.getId()); + + if (display == mTestDisplay1 || display == mTestDisplay2 + || display == mTestDisplay6 || display == mTestDisplay8 + || display == mTestDisplay9 || display == mTestDisplay10) { + display.setStatus(RemoteDisplay.STATUS_AVAILABLE); + updateDisplay(display); + } + } + + @Override + public void onSetVolume(RemoteDisplay display, int volume) { + Log.d(TAG, "onSetVolume: display.getId()=" + display.getId() + + ", volume=" + volume); + + if (display == mTestDisplay1) { + display.setVolume(Math.max(0, Math.min(display.getVolumeMax(), volume))); + updateDisplay(display); + } + } + + @Override + public void onAdjustVolume(RemoteDisplay display, int delta) { + Log.d(TAG, "onAdjustVolume: display.getId()=" + display.getId() + + ", delta=" + delta); + + if (display == mTestDisplay1) { + display.setVolume(Math.max(0, Math.min(display.getVolumeMax(), + display .getVolume() + delta))); + updateDisplay(display); + } + } + + @Override + public void addDisplay(RemoteDisplay display) { + Log.d(TAG, "addDisplay: display=" + display); + super.addDisplay(display); + } + + @Override + public void removeDisplay(RemoteDisplay display) { + Log.d(TAG, "removeDisplay: display=" + display); + super.removeDisplay(display); + } + + @Override + public void updateDisplay(RemoteDisplay display) { + Log.d(TAG, "updateDisplay: display=" + display); + super.updateDisplay(display); + } + + private final Runnable mBlink = new Runnable() { + @Override + public void run() { + if (mTestDisplay7 == null) { + if (mBlinking) { + mTestDisplay7 = new RemoteDisplay("testDisplay7", + "Test Display 7 (blinky)"); + mTestDisplay7.setDescription("Comes and goes but can't connect"); + mTestDisplay7.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay7); + mHandler.postDelayed(this, 7000); + } + } else { + removeDisplay(mTestDisplay7); + mTestDisplay7 = null; + if (mBlinking) { + mHandler.postDelayed(this, 4000); + } + } + } + }; + } +} diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index e4c42149293b..df32ee159798 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -93,7 +93,7 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.addAppToken(0, null, 0, 0, 0, false, false, 0); + mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0); fail("IWindowManager.addAppToken did not throw SecurityException as" + " expected"); } catch (SecurityException e) { diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk index 4e735686d4fd..1fa9615f6620 100644 --- a/tools/layoutlib/Android.mk +++ b/tools/layoutlib/Android.mk @@ -31,6 +31,11 @@ built_framework_classes := $(call java-lib-files,framework-base) built_core_dep := $(call java-lib-deps,core) built_core_classes := $(call java-lib-files,core) +built_ext_dep := $(call java-lib-deps,ext) +built_ext_classes := $(call java-lib-files,ext) +built_ext_data := $(call intermediates-dir-for, \ + JAVA_LIBRARIES,ext,,COMMON)/javalib.jar + built_layoutlib_create_jar := $(call intermediates-dir-for, \ JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar @@ -47,6 +52,7 @@ include $(BUILD_SYSTEM)/base_rules.mk $(LOCAL_BUILT_MODULE): $(built_core_dep) \ $(built_framework_dep) \ + $(built_ext_dep) \ $(built_layoutlib_create_jar) $(hide) echo "host layoutlib_create: $@" $(hide) mkdir -p $(dir $@) @@ -55,7 +61,9 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \ $(hide) java -jar $(built_layoutlib_create_jar) \ $@ \ $(built_core_classes) \ - $(built_framework_classes) + $(built_framework_classes) \ + $(built_ext_classes) \ + $(built_ext_data) $(hide) ls -l $(built_framework_classes) diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java index 62d0a0d6ae9d..802cf1c70828 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java +++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java @@ -20,6 +20,7 @@ import java.awt.Font; import java.awt.Graphics2D; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; +import java.awt.geom.Rectangle2D; import java.util.LinkedList; import java.util.List; @@ -50,9 +51,12 @@ public class BidiRenderer { } } - /* package */ Graphics2D graphics; - /* package */ Paint_Delegate paint; - /* package */ char[] text; + private Graphics2D mGraphics; + private Paint_Delegate mPaint; + private char[] mText; + // Bounds of the text drawn so far. + private RectF mBounds; + private float mBaseline; /** * @param graphics May be null. @@ -61,9 +65,9 @@ public class BidiRenderer { */ /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) { assert (paint != null); - this.graphics = graphics; - this.paint = paint; - this.text = text; + mGraphics = graphics; + mPaint = paint; + mText = text; } /** @@ -77,61 +81,62 @@ public class BidiRenderer { * @param advances If not null, then advances for each character to be rendered are returned * here. * @param advancesIndex index into advances from where the advances need to be filled. - * @param draw If true and {@link graphics} is not null, draw the rendered text on the graphics + * @param draw If true and {@code graphics} is not null, draw the rendered text on the graphics * at the given co-ordinates * @param x The x-coordinate of the left edge of where the text should be drawn on the given * graphics. - * @param y The y-coordinate at which to draw the text on the given graphics. - * @return The x-coordinate of the right edge of the drawn text. In other words, - * x + the width of the text. + * @param y The y-coordinate at which to draw the text on the given mGraphics. + * @return A rectangle specifying the bounds of the text drawn. */ - /* package */ float renderText(int start, int limit, boolean isRtl, float advances[], + /* package */ RectF renderText(int start, int limit, boolean isRtl, float[] advances, int advancesIndex, boolean draw, float x, float y) { // We break the text into scripts and then select font based on it and then render each of // the script runs. - for (ScriptRun run : getScriptRuns(text, start, limit, isRtl, paint.getFonts())) { + mBounds = new RectF(x, y, x, y); + mBaseline = y; + for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mPaint.getFonts())) { int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT; flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT; - x = renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw, - x, y); + renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw); advancesIndex += run.limit - run.start; } - return x; + return mBounds; } /** - * Render a script run. Use the preferred font to render as much as possible. This also - * implements a fallback mechanism to render characters that cannot be drawn using the - * preferred font. - * - * @return x + width of the text drawn. + * Render a script run to the right of the bounds passed. Use the preferred font to render as + * much as possible. This also implements a fallback mechanism to render characters that cannot + * be drawn using the preferred font. */ - private float renderScript(int start, int limit, FontInfo preferredFont, int flag, - float advances[], int advancesIndex, boolean draw, float x, float y) { - List<FontInfo> fonts = paint.getFonts(); + private void renderScript(int start, int limit, FontInfo preferredFont, int flag, + float[] advances, int advancesIndex, boolean draw) { + List<FontInfo> fonts = mPaint.getFonts(); if (fonts == null || preferredFont == null) { - return x; + return; } while (start < limit) { boolean foundFont = false; - int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(text, start, limit); + int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(mText, start, limit); if (canDisplayUpTo == -1) { - return render(start, limit, preferredFont, flag, advances, advancesIndex, draw, - x, y); - } else if (canDisplayUpTo > start) { // can draw something - x = render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, - draw, x, y); + // We can draw all characters in the text. + render(start, limit, preferredFont, flag, advances, advancesIndex, draw); + return; + } + if (canDisplayUpTo > start) { + // We can draw something. + render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, draw); advancesIndex += canDisplayUpTo - start; start = canDisplayUpTo; } - int charCount = Character.isHighSurrogate(text[start]) ? 2 : 1; + // The current character cannot be drawn with the preferred font. Cycle through all the + // fonts to check which one can draw it. + int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1; for (FontInfo font : fonts) { - canDisplayUpTo = font.mFont.canDisplayUpTo(text, start, start + charCount); + canDisplayUpTo = font.mFont.canDisplayUpTo(mText, start, start + charCount); if (canDisplayUpTo == -1) { - x = render(start, start+charCount, font, flag, advances, advancesIndex, draw, - x, y); + render(start, start+charCount, font, flag, advances, advancesIndex, draw); start += charCount; advancesIndex += charCount; foundFont = true; @@ -143,46 +148,63 @@ public class BidiRenderer { // probably appear as a box or a blank space. We could, probably, use some // heuristics and break the character into the base character and diacritics and // then draw it, but it's probably not worth the effort. - x = render(start, start + charCount, preferredFont, flag, advances, advancesIndex, - draw, x, y); + render(start, start + charCount, preferredFont, flag, advances, advancesIndex, + draw); start += charCount; advancesIndex += charCount; } } - return x; } /** - * Render the text with the given font. + * Renders the text to the right of the bounds with the given font. + * @param font The font to render the text with. */ - private float render(int start, int limit, FontInfo font, int flag, float advances[], - int advancesIndex, boolean draw, float x, float y) { + private void render(int start, int limit, FontInfo font, int flag, float[] advances, + int advancesIndex, boolean draw) { - float totalAdvance = 0; // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with // the anti-aliasing set. FontRenderContext f = font.mMetrics.getFontRenderContext(); - FontRenderContext frc = new FontRenderContext(f.getTransform(), paint.isAntiAliased(), + FontRenderContext frc = new FontRenderContext(f.getTransform(), mPaint.isAntiAliased(), f.usesFractionalMetrics()); - GlyphVector gv = font.mFont.layoutGlyphVector(frc, text, start, limit, flag); + GlyphVector gv = font.mFont.layoutGlyphVector(frc, mText, start, limit, flag); int ng = gv.getNumGlyphs(); int[] ci = gv.getGlyphCharIndices(0, ng, null); - for (int i = 0; i < ng; i++) { - float adv = gv.getGlyphMetrics(i).getAdvanceX(); - if (advances != null) { + if (advances != null) { + for (int i = 0; i < ng; i++) { int adv_idx = advancesIndex + ci[i]; - advances[adv_idx] += adv; + advances[adv_idx] += gv.getGlyphMetrics(i).getAdvanceX(); } - totalAdvance += adv; } - if (draw && graphics != null) { - graphics.drawGlyphVector(gv, x, y); + if (draw && mGraphics != null) { + mGraphics.drawGlyphVector(gv, mBounds.right, mBaseline); + } + + // Update the bounds. + Rectangle2D awtBounds = gv.getLogicalBounds(); + RectF bounds = awtRectToAndroidRect(awtBounds, mBounds.right, mBaseline); + // If the width of the bounds is zero, no text had been drawn earlier. Hence, use the + // coordinates from the bounds as an offset. + if (Math.abs(mBounds.right - mBounds.left) == 0) { + mBounds = bounds; + } else { + mBounds.union(bounds); } - return x + totalAdvance; } // --- Static helper methods --- + private static RectF awtRectToAndroidRect(Rectangle2D awtRec, float offsetX, float offsetY) { + float left = (float) awtRec.getX(); + float top = (float) awtRec.getY(); + float right = (float) (left + awtRec.getWidth()); + float bottom = (float) (top + awtRec.getHeight()); + RectF androidRect = new RectF(left, top, right, bottom); + androidRect.offset(offsetX, offsetY); + return androidRect; + } + /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit, boolean isRtl, List<FontInfo> fonts) { LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>(); diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java index 04ce9d077b53..d85c3d127bf1 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java @@ -44,62 +44,13 @@ import java.util.Set; */ /*package*/ class BitmapFactory_Delegate { - // ------ Java delegates ------ - - @LayoutlibDelegate - /*package*/ static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { - if (bm == null || opts == null) { - return bm; - } - - final int density = opts.inDensity; - if (density == 0) { - return bm; - } - - bm.setDensity(density); - final int targetDensity = opts.inTargetDensity; - if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) { - return bm; - } - - byte[] np = bm.getNinePatchChunk(); - final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); - // DELEGATE CHANGE: never scale 9-patch - if (opts.inScaled && isNinePatch == false) { - float scale = targetDensity / (float)density; - // TODO: This is very inefficient and should be done in native by Skia - final Bitmap oldBitmap = bm; - bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), - (int) (bm.getHeight() * scale + 0.5f), true); - oldBitmap.recycle(); - - if (isNinePatch) { - np = nativeScaleNinePatch(np, scale, outPadding); - bm.setNinePatchChunk(np); - } - bm.setDensity(targetDensity); - } - - return bm; - } - - // ------ Native Delegates ------ @LayoutlibDelegate /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts) { - return nativeDecodeStream(is, storage, padding, opts, false, 1.f); - } - - @LayoutlibDelegate - /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage, - Rect padding, Options opts, boolean applyScale, float scale) { Bitmap bm = null; - //TODO support rescaling - Density density = Density.MEDIUM; Set<BitmapCreateFlags> bitmapCreateFlags = EnumSet.of(BitmapCreateFlags.MUTABLE); if (opts != null) { @@ -157,13 +108,6 @@ import java.util.Set; } @LayoutlibDelegate - /*package*/ static Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts, - boolean applyScale, float scale) { - opts.inBitmap = null; - return null; - } - - @LayoutlibDelegate /*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts) { opts.inBitmap = null; @@ -171,13 +115,6 @@ import java.util.Set; } @LayoutlibDelegate - /*package*/ static byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad) { - // don't scale for now. This should not be called anyway since we re-implement - // BitmapFactory.finishDecode(); - return chunk; - } - - @LayoutlibDelegate /*package*/ static boolean nativeIsSeekable(FileDescriptor fd) { return true; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index ec284ac3cfed..13d2ba11388a 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -308,8 +308,16 @@ public final class Bitmap_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeRecycle(int nativeBitmap) { + /*package*/ static boolean nativeRecycle(int nativeBitmap) { sManager.removeJavaReferenceFor(nativeBitmap); + return true; + } + + @LayoutlibDelegate + /*package*/ static void nativeReconfigure(int nativeBitmap, int width, int height, + int config, int allocSize) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "Bitmap.reconfigure() is not supported", null /*data*/); } @LayoutlibDelegate @@ -341,28 +349,6 @@ public final class Bitmap_Delegate { } @LayoutlibDelegate - /*package*/ static int nativeWidth(int nativeBitmap) { - // get the delegate from the native int. - Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); - if (delegate == null) { - return 0; - } - - return delegate.mImage.getWidth(); - } - - @LayoutlibDelegate - /*package*/ static int nativeHeight(int nativeBitmap) { - // get the delegate from the native int. - Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); - if (delegate == null) { - return 0; - } - - return delegate.mImage.getHeight(); - } - - @LayoutlibDelegate /*package*/ static int nativeRowBytes(int nativeBitmap) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); @@ -407,19 +393,21 @@ public final class Bitmap_Delegate { } @LayoutlibDelegate - /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) { + /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y, + boolean isPremultiplied) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return 0; } + // TODO: Support isPremultiplied. return delegate.mImage.getRGB(x, y); } @LayoutlibDelegate /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset, - int stride, int x, int y, int width, int height) { + int stride, int x, int y, int width, int height, boolean isPremultiplied) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; @@ -430,7 +418,8 @@ public final class Bitmap_Delegate { @LayoutlibDelegate - /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) { + /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color, + boolean isPremultiplied) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; @@ -441,7 +430,7 @@ public final class Bitmap_Delegate { @LayoutlibDelegate /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset, - int stride, int x, int y, int width, int height) { + int stride, int x, int y, int width, int height, boolean isPremultiplied) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index 62b47bd6a0f2..10ad0a3f5a1f 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -990,7 +990,8 @@ public final class Canvas_Delegate { int limit = index + count; boolean isRtl = flags == Canvas.DIRECTION_RTL; if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { - float m = paintDelegate.measureText(text, index, count, isRtl); + RectF bounds = paintDelegate.measureText(text, index, count, isRtl); + float m = bounds.right - bounds.left; if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { x -= m / 2; } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java index fa687962719e..a79ec8fde741 100644 --- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java @@ -167,6 +167,7 @@ public final class NinePatch_Delegate { return sManager.addNewDelegate(newDelegate); } + @LayoutlibDelegate /*package*/ static void nativeFinalize(int chunk) { sManager.removeJavaReferenceFor(chunk); } @@ -175,7 +176,7 @@ public final class NinePatch_Delegate { /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance, int chunk, int paint_instance_or_null, int destDensity, int srcDensity) { draw(canvas_instance, - (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(), + (int) loc.left, (int) loc.top, (int) loc.right, (int) loc.bottom, bitmap_instance, chunk, paint_instance_or_null, destDensity, srcDensity); } @@ -184,7 +185,7 @@ public final class NinePatch_Delegate { /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance, int chunk, int paint_instance_or_null, int destDensity, int srcDensity) { draw(canvas_instance, - loc.left, loc.top, loc.width(), loc.height(), + loc.left, loc.top, loc.right, loc.bottom, bitmap_instance, chunk, paint_instance_or_null, destDensity, srcDensity); } diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 41953ed28011..4ad1a175853b 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -575,7 +575,8 @@ public class Paint_Delegate { return 0; } - return delegate.measureText(text, index, count, isRtl(bidiFlags)); + RectF bounds = delegate.measureText(text, index, count, isRtl(bidiFlags)); + return bounds.right - bounds.left; } @LayoutlibDelegate @@ -614,7 +615,8 @@ public class Paint_Delegate { } // measure from start to end - float res = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags)); + RectF bounds = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags)); + float res = bounds.right - bounds.left; if (measuredWidth != null) { measuredWidth[measureIndex] = res; @@ -991,8 +993,9 @@ public class Paint_Delegate { boolean isRtl = isRtl(flags); int limit = index + count; - return new BidiRenderer(null, delegate, text).renderText( + RectF bounds = new BidiRenderer(null, delegate, text).renderText( index, limit, isRtl, advances, advancesIndex, false, 0, 0); + return bounds.right - bounds.left; } @LayoutlibDelegate @@ -1058,9 +1061,7 @@ public class Paint_Delegate { if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { return; } - int w = (int) delegate.measureText(text, index, count, isRtl(bidiFlags)); - int h= delegate.getFonts().get(0).mMetrics.getHeight(); - bounds.set(0, 0, w, h); + delegate.measureText(text, index, count, isRtl(bidiFlags)).roundOut(bounds); } @LayoutlibDelegate @@ -1154,7 +1155,7 @@ public class Paint_Delegate { } } - /*package*/ float measureText(char[] text, int index, int count, boolean isRtl) { + /*package*/ RectF measureText(char[] text, int index, int count, boolean isRtl) { return new BidiRenderer(null, this, text).renderText( index, index + count, isRtl, null, 0, false, 0, 0); } diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java index 64f19d310d2f..9d80be922195 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -474,6 +474,12 @@ public final class Path_Delegate { } @LayoutlibDelegate + /*package*/ static boolean native_op(int nPath1, int nPath2, int op, int result) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null); + return false; + } + + @LayoutlibDelegate /*package*/ static void finalizer(int nPath) { sManager.removeJavaReferenceFor(nPath); } diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index 8701cc8068a6..b50e98b8746a 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -103,6 +103,9 @@ public final class Typeface_Delegate { if (familyName == null) { familyName = DEFAULT_FAMILY; } + if (style < 0) { + style = Typeface.NORMAL; + } Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style); if (sFontLoader != null) { diff --git a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java index 15cd68789863..320dd0dcb664 100644 --- a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java @@ -17,6 +17,7 @@ package android.text.format; import java.util.Calendar; +import java.util.TimeZone; import java.util.UnknownFormatConversionException; import java.util.regex.Pattern; @@ -35,6 +36,28 @@ public class Time_Delegate { // Regex to match odd number of '%'. private static final Pattern p = Pattern.compile("(?<!%)(%%)*%(?!%)"); + // Format used by toString() + private static final String FORMAT = "%1$tY%1$tm%1$tdT%1$tH%1$tM%1$tS<%1$tZ>"; + + @LayoutlibDelegate + /*package*/ static long normalize(Time thisTime, boolean ignoreDst) { + long millis = toMillis(thisTime, ignoreDst); + set(thisTime, millis); + return millis; + } + + @LayoutlibDelegate + /*package*/ static void switchTimezone(Time thisTime, String timezone) { + Calendar c = timeToCalendar(thisTime); + c.setTimeZone(TimeZone.getTimeZone(timezone)); + calendarToTime(c, thisTime); + } + + @LayoutlibDelegate + /*package*/ static int nativeCompare(Time a, Time b) { + return timeToCalendar(a).compareTo(timeToCalendar(b)); + } + @LayoutlibDelegate /*package*/ static String format1(Time thisTime, String format) { @@ -46,16 +69,92 @@ public class Time_Delegate { // of $. return String.format( p.matcher(format).replaceAll("$0\\1\\$t"), - timeToCalendar(thisTime, Calendar.getInstance())); + timeToCalendar(thisTime)); } catch (UnknownFormatConversionException e) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_STRFTIME, "Unrecognized format", e, format); return format; } } - private static Calendar timeToCalendar(Time time, Calendar calendar) { + /** + * Return the current time in YYYYMMDDTHHMMSS<tz> format + */ + @LayoutlibDelegate + /*package*/ static String toString(Time thisTime) { + Calendar c = timeToCalendar(thisTime); + return String.format(FORMAT, c); + } + + @LayoutlibDelegate + /*package*/ static boolean nativeParse(Time thisTime, String s) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "android.text.format.Time.parse() not supported.", null); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean nativeParse3339(Time thisTime, String s) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "android.text.format.Time.parse3339() not supported.", null); + return false; + } + + @LayoutlibDelegate + /*package*/ static void setToNow(Time thisTime) { + calendarToTime(getCalendarInstance(thisTime), thisTime); + } + + @LayoutlibDelegate + /*package*/ static long toMillis(Time thisTime, boolean ignoreDst) { + // TODO: Respect ignoreDst. + return timeToCalendar(thisTime).getTimeInMillis(); + } + + @LayoutlibDelegate + /*package*/ static void set(Time thisTime, long millis) { + Calendar c = getCalendarInstance(thisTime); + c.setTimeInMillis(millis); + calendarToTime(c,thisTime); + } + + @LayoutlibDelegate + /*package*/ static String format2445(Time thisTime) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, + "android.text.format.Time.format2445() not supported.", null); + return ""; + } + + // ---- private helper methods ---- + + private static Calendar timeToCalendar(Time time) { + Calendar calendar = getCalendarInstance(time); calendar.set(time.year, time.month, time.monthDay, time.hour, time.minute, time.second); return calendar; } + private static void calendarToTime(Calendar c, Time time) { + time.timezone = c.getTimeZone().getID(); + time.set(c.get(Calendar.SECOND), c.get(Calendar.MINUTE), c.get(Calendar.HOUR_OF_DAY), + c.get(Calendar.DATE), c.get(Calendar.MONTH), c.get(Calendar.YEAR)); + time.weekDay = c.get(Calendar.DAY_OF_WEEK); + time.yearDay = c.get(Calendar.DAY_OF_YEAR); + time.isDst = c.getTimeZone().inDaylightTime(c.getTime()) ? 1 : 0; + // gmtoff is in seconds and TimeZone.getOffset() returns milliseconds. + time.gmtoff = c.getTimeZone().getOffset(c.getTimeInMillis()) / DateUtils.SECOND_IN_MILLIS; + } + + /** + * Return a calendar instance with the correct timezone. + * + * @param time Time to obtain the timezone from. + */ + private static Calendar getCalendarInstance(Time time) { + // TODO: Check platform code to make sure the behavior is same for null/invalid timezone. + if (time == null || time.timezone == null) { + // Default to local timezone. + return Calendar.getInstance(); + } + // If timezone is invalid, use GMT. + return Calendar.getInstance(TimeZone.getTimeZone(time.timezone)); + } } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index fd153af99353..dd2cbc10d203 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -81,7 +81,7 @@ public class IWindowManagerImpl implements IWindowManager { @Override public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4, - boolean arg5, boolean arg6, int arg7) + boolean arg5, boolean arg6, int arg7, int arg8) throws RemoteException { // TODO Auto-generated method stub diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index 17b0eb641efa..bcd08eb47070 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -290,7 +290,7 @@ abstract class CustomBar extends LinearLayout { TypedValue out = new TypedValue(); if (ResourceHelper.parseFloatAttribute("textSize", textSize.getValue(), out, true /*requireUnit*/)) { - textView.setTextSize( + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, out.getDimension(bridgeContext.getResources().getDisplayMetrics())); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java index 108b651e302c..cc7338ae8c32 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java @@ -57,7 +57,9 @@ public final class FontLoader { private static final String FONT_SUFFIX_NONE = ".ttf"; private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf"; private static final String FONT_SUFFIX_BOLD = "-Bold.ttf"; - private static final String FONT_SUFFIX_ITALIC = "-Italic.ttf"; + // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked + // separately. + private static final String FONT_SUFFIX_ITALIC = "Italic.ttf"; private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf"; // This must match the values of Typeface styles so that we can use them for indices in this @@ -285,10 +287,10 @@ public final class FontLoader { mFontInfo.font[Typeface.NORMAL] = font; } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) { mFontInfo.font[Typeface.BOLD] = font; - } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) { - mFontInfo.font[Typeface.ITALIC] = font; } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) { mFontInfo.font[Typeface.BOLD_ITALIC] = font; + } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) { + mFontInfo.font[Typeface.ITALIC] = font; } else if (fileName.endsWith(FONT_SUFFIX_NONE)) { mFontInfo.font[Typeface.NORMAL] = font; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index 23d08e343bdb..60f5331aefdb 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -273,7 +273,6 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso mContext.getRenderResources().setLogger(null); } - mContext = null; } public static BridgeContext getCurrentContext() { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 57771e326a2b..377d99664fb7 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -334,7 +334,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { backgroundView = backgroundLayout; backgroundLayout.setOrientation(LinearLayout.VERTICAL); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + LayoutParams.MATCH_PARENT, 0); layoutParams.weight = 1; backgroundLayout.setLayoutParams(layoutParams); topLayout.addView(backgroundLayout); @@ -369,7 +369,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // content frame mContentRoot = new FrameLayout(context); layoutParams = new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + LayoutParams.MATCH_PARENT, 0); layoutParams.weight = 1; mContentRoot.setLayoutParams(layoutParams); backgroundLayout.addView(mContentRoot); diff --git a/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java index a773d93602d9..d94c205df5f5 100644 --- a/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java +++ b/tools/layoutlib/bridge/src/libcore/icu/DateIntervalFormat_Delegate.java @@ -21,6 +21,7 @@ import java.text.FieldPosition; import com.android.ide.common.rendering.api.LayoutLog; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import com.ibm.icu.text.DateIntervalFormat; import com.ibm.icu.util.DateInterval; import com.ibm.icu.util.TimeZone; @@ -38,6 +39,7 @@ public class DateIntervalFormat_Delegate { // ---- native methods ---- + @LayoutlibDelegate /*package*/static String formatDateInterval(long address, long fromDate, long toDate) { DateIntervalFormat_Delegate delegate = sManager.getDelegate((int)address); if (delegate == null) { @@ -52,6 +54,7 @@ public class DateIntervalFormat_Delegate { return sb.toString(); } + @LayoutlibDelegate /*package*/ static long createDateIntervalFormat(String skeleton, String localeName, String tzName) { TimeZone prevDefaultTz = TimeZone.getDefault(); @@ -63,6 +66,7 @@ public class DateIntervalFormat_Delegate { return sManager.addNewDelegate(newDelegate); } + @LayoutlibDelegate /*package*/ static void destroyDateIntervalFormat(long address) { sManager.removeJavaReferenceFor((int)address); } diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java index 06ae804aa7c6..ad4103bf747c 100644 --- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java +++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java @@ -46,7 +46,7 @@ public class ICU_Delegate { // --- Native methods accessing ICU's database. @LayoutlibDelegate - /*package*/ static String getBestDateTimePattern(String skeleton, String localeName) { + /*package*/ static String getBestDateTimePatternNative(String skeleton, String localeName) { return DateTimePatternGenerator.getInstance(new ULocale(localeName)) .getBestPattern(skeleton); } @@ -167,7 +167,7 @@ public class ICU_Delegate { } @LayoutlibDelegate - /*package*/ static boolean initLocaleDataImpl(String locale, LocaleData result) { + /*package*/ static boolean initLocaleDataNative(String locale, LocaleData result) { // Used by Calendar. result.firstDayOfWeek = Integer.valueOf(1); diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java index 1572a4034432..9a31705d0a3a 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java @@ -29,6 +29,7 @@ import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -60,6 +61,9 @@ public class AsmAnalyzer { private final String[] mIncludeGlobs; /** The set of classes to exclude.*/ private final Set<String> mExcludedClasses; + /** Glob patterns of files to keep as is. */ + private final String[] mIncludeFileGlobs; + /** Copy these files into the output as is. */ /** * Creates a new analyzer. @@ -70,15 +74,19 @@ public class AsmAnalyzer { * @param deriveFrom Keep all classes that derive from these one (these included). * @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*" * ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is) + * @param includeFileGlobs Glob patterns of files which are kept as is. This is only for files + * not ending in .class. */ public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen, - String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses) { + String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses, + String[] includeFileGlobs) { mLog = log; mGen = gen; mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>(); mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0]; mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0]; mExcludedClasses = excludeClasses; + mIncludeFileGlobs = includeFileGlobs != null ? includeFileGlobs : new String[0]; } /** @@ -86,7 +94,11 @@ public class AsmAnalyzer { * Fills the generator with classes & dependencies found. */ public void analyze() throws IOException, LogAbortException { - Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar); + + TreeMap<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); + Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + + parseZip(mOsSourceJar, zipClasses, filesFound); mLog.info("Found %d classes in input JAR%s.", zipClasses.size(), mOsSourceJar.size() > 1 ? "s" : ""); @@ -96,15 +108,29 @@ public class AsmAnalyzer { if (mGen != null) { mGen.setKeep(found); mGen.setDeps(deps); + mGen.setCopyFiles(filesFound); } } /** - * Parses a JAR file and returns a list of all classes founds using a map - * class name => ASM ClassReader. Class names are in the form "android.view.View". + * Parses a JAR file and adds all the classes found to <code>classes</code> + * and all other files to <code>filesFound</code>. + * + * @param classes The map of class name => ASM ClassReader. Class names are + * in the form "android.view.View". + * @param fileFound The map of file name => InputStream. The file name is + * in the form "android/data/dataFile". */ - Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException { - TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>(); + void parseZip(List<String> jarPathList, Map<String, ClassReader> classes, + Map<String, InputStream> filesFound) throws IOException { + if (classes == null || filesFound == null) { + return; + } + + Pattern[] includeFilePatterns = new Pattern[mIncludeFileGlobs.length]; + for (int i = 0; i < mIncludeFileGlobs.length; ++i) { + includeFilePatterns[i] = getPatternFromGlob(mIncludeFileGlobs[i]); + } for (String jarPath : jarPathList) { ZipFile zip = new ZipFile(jarPath); @@ -116,11 +142,17 @@ public class AsmAnalyzer { ClassReader cr = new ClassReader(zip.getInputStream(entry)); String className = classReaderToClassName(cr); classes.put(className, cr); + } else { + for (int i = 0; i < includeFilePatterns.length; ++i) { + if (includeFilePatterns[i].matcher(entry.getName()).matches()) { + filesFound.put(entry.getName(), zip.getInputStream(entry)); + break; + } + } } } } - return classes; } /** @@ -202,7 +234,19 @@ public class AsmAnalyzer { */ void findGlobs(String globPattern, Map<String, ClassReader> zipClasses, Map<String, ClassReader> inOutFound) throws LogAbortException { - // transforms the glob pattern in a regexp: + + Pattern regexp = getPatternFromGlob(globPattern); + + for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { + String class_name = entry.getKey(); + if (regexp.matcher(class_name).matches()) { + findClass(class_name, zipClasses, inOutFound); + } + } + } + + Pattern getPatternFromGlob(String globPattern) { + // transforms the glob pattern in a regexp: // - escape "." with "\." // - replace "*" by "[^.]*" // - escape "$" with "\$" @@ -216,14 +260,7 @@ public class AsmAnalyzer { globPattern = globPattern.replaceAll("@", ".*"); globPattern += "$"; - Pattern regexp = Pattern.compile(globPattern); - - for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { - String class_name = entry.getKey(); - if (regexp.matcher(class_name).matches()) { - findClass(class_name, zipClasses, inOutFound); - } - } + return Pattern.compile(globPattern); } /** diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index b10256127ae8..207d8ae7b2d9 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -20,6 +20,7 @@ import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -52,6 +53,8 @@ public class AsmGenerator { private Map<String, ClassReader> mKeep; /** All dependencies that must be completely stubbed. */ private Map<String, ClassReader> mDeps; + /** All files that are to be copied as-is. */ + private Map<String, InputStream> mCopyFiles; /** Counter of number of classes renamed during transform. */ private int mRenameCount; /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */ @@ -195,6 +198,11 @@ public class AsmGenerator { mDeps = deps; } + /** Sets the map of files to output as-is. */ + public void setCopyFiles(Map<String, InputStream> copyFiles) { + mCopyFiles = copyFiles; + } + /** Gets the map of classes to output as-is, except if they have native methods */ public Map<String, ClassReader> getKeep() { return mKeep; @@ -205,6 +213,11 @@ public class AsmGenerator { return mDeps; } + /** Gets the map of files to output as-is. */ + public Map<String, InputStream> getCopyFiles() { + return mCopyFiles; + } + /** Generates the final JAR */ public void generate() throws FileNotFoundException, IOException { TreeMap<String, byte[]> all = new TreeMap<String, byte[]>(); @@ -232,6 +245,15 @@ public class AsmGenerator { all.put(name, b); } + for (Entry<String, InputStream> entry : mCopyFiles.entrySet()) { + try { + byte[] b = inputStreamToByteArray(entry.getValue()); + all.put(entry.getKey(), b); + } catch (IOException e) { + // Ignore. + } + + } mLog.info("# deps classes: %d", mDeps.size()); mLog.info("# keep classes: %d", mKeep.size()); mLog.info("# renamed : %d", mRenameCount); @@ -381,4 +403,13 @@ public class AsmGenerator { return cv.hasNativeMethods(); } + private byte[] inputStreamToByteArray(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] data = new byte[8192]; // 8KB + int n; + while ((n = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, n); + } + return buffer.toByteArray(); + } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index f6779e38ccfa..79aa642b7024 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -131,7 +131,6 @@ public final class CreateInfo implements ICreateInfo { "android.os.HandlerThread#run", "android.os.Build#getString", "android.text.format.DateFormat#is24HourFormat", - "android.text.format.Time#format1", "android.view.Choreographer#getRefreshRate", "android.view.Display#updateDisplayInfoLocked", "android.view.LayoutInflater#rInflate", @@ -188,6 +187,7 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.Xfermode", "android.os.SystemClock", "android.text.AndroidBidi", + "android.text.format.Time", "android.util.FloatMath", "android.view.Display", "libcore.icu.DateIntervalFormat", diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index ba23048aee7b..a79fba19d216 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -113,8 +113,12 @@ public class Main { "android.pim.*", // for datepicker "android.os.*", // for android.os.Handler "android.database.ContentObserver", // for Digital clock + "com.android.i18n.phonenumbers.*", // for TextView with autolink attribute }, - excludeClasses); + excludeClasses, + new String[] { + "com/android/i18n/phonenumbers/data/*", + }); aa.analyze(); agen.generate(); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java index 005fc9dadab4..7ec0d389be87 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java @@ -29,6 +29,7 @@ import org.junit.Test; import org.objectweb.asm.ClassReader; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; @@ -55,8 +56,10 @@ public class AsmAnalyzerTest { Set<String> excludeClasses = new HashSet<String>(1); excludeClasses.add("java.lang.JavaClass"); - mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, - null /* deriveFrom */, null /* includeGlobs */, excludeClasses); + + String[] includeFiles = new String[]{"mock_android/data/data*"}; + mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, null /* deriveFrom */, + null /* includeGlobs */, excludeClasses, includeFiles); } @After @@ -65,7 +68,11 @@ public class AsmAnalyzerTest { @Test public void testParseZip() throws IOException { - Map<String, ClassReader> map = mAa.parseZip(mOsJarPath); + + Map<String, ClassReader> map = new TreeMap<String, ClassReader>(); + Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + + mAa.parseZip(mOsJarPath, map, filesFound); assertArrayEquals(new String[] { "java.lang.JavaClass", @@ -86,11 +93,17 @@ public class AsmAnalyzerTest { "mock_android.widget.TableLayout$LayoutParams" }, map.keySet().toArray()); + assertArrayEquals(new String[] {"mock_android/data/dataFile"}, + filesFound.keySet().toArray()); } @Test public void testFindClass() throws IOException, LogAbortException { - Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath); + + Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); + Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + + mAa.parseZip(mOsJarPath, zipClasses, filesFound); TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>(); ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams", @@ -105,7 +118,11 @@ public class AsmAnalyzerTest { @Test public void testFindGlobs() throws IOException, LogAbortException { - Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath); + + Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); + Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + + mAa.parseZip(mOsJarPath, zipClasses, filesFound); TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>(); // this matches classes, a package match returns nothing @@ -164,7 +181,11 @@ public class AsmAnalyzerTest { @Test public void testFindClassesDerivingFrom() throws LogAbortException, IOException { - Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath); + + Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); + Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + + mAa.parseZip(mOsJarPath, zipClasses, filesFound); TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>(); mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found); @@ -186,7 +207,11 @@ public class AsmAnalyzerTest { @Test public void testDependencyVisitor() throws IOException, LogAbortException { - Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath); + + Map<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>(); + Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + + mAa.parseZip(mOsJarPath, zipClasses, filesFound); TreeMap<String, ClassReader> keep = new TreeMap<String, ClassReader>(); TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>(); TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>(); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java index 8a27173181a3..0dbc2387b128 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -33,6 +33,7 @@ import org.objectweb.asm.Type; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; @@ -131,7 +132,8 @@ public class AsmGeneratorTest { new String[] { // include classes "**" }, - new HashSet<String>(0) /* excluded classes */); + new HashSet<String>(0) /* excluded classes */, + new String[]{} /* include files */); aa.analyze(); agen.generate(); @@ -195,10 +197,15 @@ public class AsmGeneratorTest { new String[] { // include classes "**" }, - new HashSet<String>(1)); + new HashSet<String>(1), + new String[] { /* include files */ + "mock_android/data/data*" + }); aa.analyze(); agen.generate(); - Map<String, ClassReader> output = parseZip(mOsDestJar); + Map<String, ClassReader> output = new TreeMap<String, ClassReader>(); + Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + parseZip(mOsDestJar, output, filesFound); boolean injectedClassFound = false; for (ClassReader cr: output.values()) { TestClassVisitor cv = new TestClassVisitor(); @@ -206,10 +213,13 @@ public class AsmGeneratorTest { injectedClassFound |= cv.mInjectedClassFound; } assertTrue(injectedClassFound); + assertArrayEquals(new String[] {"mock_android/data/dataFile"}, + filesFound.keySet().toArray()); } - private Map<String,ClassReader> parseZip(String jarPath) throws IOException { - TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>(); + private void parseZip(String jarPath, + Map<String, ClassReader> classes, + Map<String, InputStream> filesFound) throws IOException { ZipFile zip = new ZipFile(jarPath); Enumeration<? extends ZipEntry> entries = zip.entries(); @@ -220,10 +230,11 @@ public class AsmGeneratorTest { ClassReader cr = new ClassReader(zip.getInputStream(entry)); String className = classReaderToClassName(cr); classes.put(className, cr); + } else { + filesFound.put(entry.getName(), zip.getInputStream(entry)); } } - return classes; } private String classReaderToClassName(ClassReader classReader) { diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar Binary files differindex 60d8efb1bb99..8dd04812a866 100644 --- a/tools/layoutlib/create/tests/data/mock_android.jar +++ b/tools/layoutlib/create/tests/data/mock_android.jar diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile new file mode 100644 index 000000000000..ab29fbe449cf --- /dev/null +++ b/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile @@ -0,0 +1 @@ +A simple data file that should *not* be copied to the output jar.
\ No newline at end of file diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile new file mode 100644 index 000000000000..9b01893ebab3 --- /dev/null +++ b/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile @@ -0,0 +1 @@ +A simple data file that should be copied to the output jar unchanged.
\ No newline at end of file diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 5a1928c58b9c..149bda6a18b7 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -117,7 +117,7 @@ interface IWifiManager void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable); - boolean requestBatchedScan(in BatchedScanSettings requested, IBinder binder); + boolean requestBatchedScan(in BatchedScanSettings requested, IBinder binder, in WorkSource ws); void stopBatchedScan(in BatchedScanSettings requested); diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java index f6a621fa297c..e76eb174e561 100644 --- a/wifi/java/android/net/wifi/SupplicantStateTracker.java +++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java @@ -55,7 +55,7 @@ class SupplicantStateTracker extends StateMachine { private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2; /* Maximum retries on assoc rejection events */ - private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 4; + private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 16; /* Tracks if networks have been disabled during a connection */ private boolean mNetworksDisabledDuringConnect = false; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 7fc8bef5cec4..3b47e635c765 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -799,7 +799,13 @@ public class WifiManager { */ public boolean requestBatchedScan(BatchedScanSettings requested) { try { - return mService.requestBatchedScan(requested, new Binder()); + return mService.requestBatchedScan(requested, new Binder(), null); + } catch (RemoteException e) { return false; } + } + /** @hide */ + public boolean requestBatchedScan(BatchedScanSettings requested, WorkSource workSource) { + try { + return mService.requestBatchedScan(requested, new Binder(), workSource); } catch (RemoteException e) { return false; } } diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 520668e50941..c2f278ab98df 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -39,7 +39,6 @@ import java.util.Locale; public class WifiNative { private static final boolean DBG = false; - private static final boolean VDBG = false; private final String mTAG; private static final int DEFAULT_GROUP_OWNER_INTENT = 6; @@ -118,12 +117,12 @@ public class WifiNative { public boolean connectToSupplicant() { // No synchronization necessary .. it is implemented in WifiMonitor - if (VDBG) localLog(mInterfacePrefix + "connectToSupplicant"); + localLog(mInterfacePrefix + "connectToSupplicant"); return connectToSupplicantNative(); } public void closeSupplicantConnection() { - if (VDBG) localLog(mInterfacePrefix + "closeSupplicantConnection"); + localLog(mInterfacePrefix + "closeSupplicantConnection"); closeSupplicantConnectionNative(); } @@ -136,9 +135,9 @@ public class WifiNative { if (DBG) Log.d(mTAG, "doBoolean: " + command); synchronized (mLock) { int cmdId = getNewCmdIdLocked(); - if (VDBG) localLog(cmdId + "->" + mInterfacePrefix + command); + localLog(cmdId + "->" + mInterfacePrefix + command); boolean result = doBooleanCommandNative(mInterfacePrefix + command); - if (VDBG) localLog(cmdId + "<-" + result); + localLog(cmdId + "<-" + result); if (DBG) Log.d(mTAG, " returned " + result); return result; } @@ -148,9 +147,9 @@ public class WifiNative { if (DBG) Log.d(mTAG, "doInt: " + command); synchronized (mLock) { int cmdId = getNewCmdIdLocked(); - if (VDBG) localLog(cmdId + "->" + mInterfacePrefix + command); + localLog(cmdId + "->" + mInterfacePrefix + command); int result = doIntCommandNative(mInterfacePrefix + command); - if (VDBG) localLog(cmdId + "<-" + result); + localLog(cmdId + "<-" + result); if (DBG) Log.d(mTAG, " returned " + result); return result; } @@ -160,9 +159,9 @@ public class WifiNative { if (DBG) Log.d(mTAG, "doString: " + command); synchronized (mLock) { int cmdId = getNewCmdIdLocked(); - if (VDBG) localLog(cmdId + "->" + mInterfacePrefix + command); + localLog(cmdId + "->" + mInterfacePrefix + command); String result = doStringCommandNative(mInterfacePrefix + command); - if (VDBG) localLog(cmdId + "<-" + result); + localLog(cmdId + "<-" + result); if (DBG) Log.d(mTAG, " returned " + result); return result; } @@ -298,7 +297,9 @@ public class WifiNative { * the firmware can remember before it runs out of buffer space or -1 on error */ public String setBatchedScanSettings(BatchedScanSettings settings) { - if (settings == null) return doStringCommand("DRIVER WLS_BATCHING STOP"); + if (settings == null) { + return doStringCommand("DRIVER WLS_BATCHING STOP"); + } String cmd = "DRIVER WLS_BATCHING SET SCANFREQ=" + settings.scanIntervalSec; cmd += " MSCAN=" + settings.maxScansPerBatch; if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) { diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 78da7e7c7194..d6b23ae692a9 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -57,6 +57,7 @@ import android.net.wifi.p2p.WifiP2pManager; import android.net.wifi.p2p.WifiP2pService; import android.os.BatteryStats; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.Message; @@ -232,6 +233,10 @@ public class WifiStateMachine extends StateMachine { private DhcpStateMachine mDhcpStateMachine; private boolean mDhcpActive = false; + // Delay in switching to null country code (non-null has no delay) + private final int COUNTRY_CODE_DELAY_MS = 15000; + private final AtomicInteger mCountryCodeSequence = new AtomicInteger(); + private class InterfaceObserver extends BaseNetworkObserver { private WifiStateMachine mWifiStateMachine; @@ -416,7 +421,8 @@ public class WifiStateMachine extends StateMachine { /* change the batch scan settings. * arg1 = responsible UID - * obj = the new settings + * arg2 = csph (channel scans per hour) + * obj = bundle with the new settings and the optional worksource */ public static final int CMD_SET_BATCHED_SCAN = BASE + 135; public static final int CMD_START_NEXT_BATCHED_SCAN = BASE + 136; @@ -628,6 +634,15 @@ public class WifiStateMachine extends StateMachine { private BatchedScanSettings mBatchedScanSettings = null; + /** + * Track the worksource/cost of the current settings and track what's been noted + * to the battery stats, so we can mark the end of the previous when changing. + */ + private WorkSource mBatchedScanWorkSource = null; + private int mBatchedScanCsph = 0; + private WorkSource mNotedBatchedScanWorkSource = null; + private int mNotedBatchedScanCsph = 0; + public WifiStateMachine(Context context, String wlanInterface) { super("WifiStateMachine"); @@ -841,8 +856,14 @@ public class WifiStateMachine extends StateMachine { /** * start or stop batched scanning using the given settings */ - public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid) { - sendMessage(CMD_SET_BATCHED_SCAN, callingUid, 0, settings); + private static final String BATCHED_SETTING = "batched_settings"; + private static final String BATCHED_WORKSOURCE = "batched_worksource"; + public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid, int csph, + WorkSource workSource) { + Bundle bundle = new Bundle(); + bundle.putParcelable(BATCHED_SETTING, settings); + bundle.putParcelable(BATCHED_WORKSOURCE, workSource); + sendMessage(CMD_SET_BATCHED_SCAN, callingUid, csph, bundle); } public List<BatchedScanResult> syncGetBatchedScanResultsList() { @@ -861,6 +882,8 @@ public class WifiStateMachine extends StateMachine { } private void startBatchedScan() { + if (mBatchedScanSettings == null) return; + if (mDhcpActive) { if (DBG) log("not starting Batched Scans due to DHCP"); return; @@ -872,10 +895,10 @@ public class WifiStateMachine extends StateMachine { mAlarmManager.cancel(mBatchedScanIntervalIntent); String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings); - try { mExpectedBatchedScans = Integer.parseInt(scansExpected); setNextBatchedAlarm(mExpectedBatchedScans); + if (mExpectedBatchedScans > 0) noteBatchedScanStart(); } catch (NumberFormatException e) { stopBatchedScan(); loge("Exception parsing WifiNative.setBatchedScanSettings response " + e); @@ -918,25 +941,31 @@ public class WifiStateMachine extends StateMachine { } // return true if new/different - private boolean recordBatchedScanSettings(BatchedScanSettings settings) { - if (DBG) log("set batched scan to " + settings); + private boolean recordBatchedScanSettings(int responsibleUid, int csph, Bundle bundle) { + BatchedScanSettings settings = bundle.getParcelable(BATCHED_SETTING); + WorkSource responsibleWorkSource = bundle.getParcelable(BATCHED_WORKSOURCE); + + if (DBG) { + log("set batched scan to " + settings + " for uid=" + responsibleUid + + ", worksource=" + responsibleWorkSource); + } if (settings != null) { - // TODO - noteBatchedScanStart(message.arg1); if (settings.equals(mBatchedScanSettings)) return false; } else { if (mBatchedScanSettings == null) return false; - // TODO - noteBatchedScanStop(message.arg1); } mBatchedScanSettings = settings; + if (responsibleWorkSource == null) responsibleWorkSource = new WorkSource(responsibleUid); + mBatchedScanWorkSource = responsibleWorkSource; + mBatchedScanCsph = csph; return true; } private void stopBatchedScan() { mAlarmManager.cancel(mBatchedScanIntervalIntent); - if (mBatchedScanSettings != null) { - retrieveBatchedScanData(); - mWifiNative.setBatchedScanSettings(null); - } + retrieveBatchedScanData(); + mWifiNative.setBatchedScanSettings(null); + noteBatchedScanStop(); } private void setNextBatchedAlarm(int scansExpected) { @@ -1164,6 +1193,44 @@ public class WifiStateMachine extends StateMachine { } } + private void noteBatchedScanStart() { + // note the end of a previous scan set + if (mNotedBatchedScanWorkSource != null && + (mNotedBatchedScanWorkSource.equals(mBatchedScanWorkSource) == false || + mNotedBatchedScanCsph != mBatchedScanCsph)) { + try { + mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource); + } catch (RemoteException e) { + log(e.toString()); + } finally { + mNotedBatchedScanWorkSource = null; + mNotedBatchedScanCsph = 0; + } + } + // note the start of the new + try { + mBatteryStats.noteWifiBatchedScanStartedFromSource(mBatchedScanWorkSource, + mBatchedScanCsph); + mNotedBatchedScanWorkSource = mBatchedScanWorkSource; + mNotedBatchedScanCsph = mBatchedScanCsph; + } catch (RemoteException e) { + log(e.toString()); + } + } + + private void noteBatchedScanStop() { + if (mNotedBatchedScanWorkSource != null) { + try { + mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource); + } catch (RemoteException e) { + log(e.toString()); + } finally { + mNotedBatchedScanWorkSource = null; + mNotedBatchedScanCsph = 0; + } + } + } + private void startScanNative(int type) { mWifiNative.scan(type); mScanResultIsPending = true; @@ -1471,14 +1538,18 @@ public class WifiStateMachine extends StateMachine { * @param persist {@code true} if the setting should be remembered. */ public void setCountryCode(String countryCode, boolean persist) { - if (persist) { - mPersistedCountryCode = countryCode; - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.WIFI_COUNTRY_CODE, - countryCode); + // If it's a country code, apply immediately, + // If it's empty, delay it in case it's a momentary dropout + int countryCodeSequence = mCountryCodeSequence.incrementAndGet(); + if (TextUtils.isEmpty(countryCode)) { + String defaultCountryCode = mContext.getResources().getString( + R.string.config_wifi_unknown_country_code); + + sendMessageDelayed(CMD_SET_COUNTRY_CODE, countryCodeSequence, persist ? 1 : 0, + defaultCountryCode, COUNTRY_CODE_DELAY_MS); + } else { + sendMessage(CMD_SET_COUNTRY_CODE, countryCodeSequence, persist ? 1 : 0, countryCode); } - sendMessage(CMD_SET_COUNTRY_CODE, countryCode); - mWifiP2pChannel.sendMessage(WifiP2pService.SET_COUNTRY_CODE, countryCode); } /** @@ -1868,6 +1939,9 @@ public class WifiStateMachine extends StateMachine { return; } + // note that all these splits and substrings keep references to the original + // huge string buffer while the amount we really want is generally pretty small + // so make copies instead (one example b/11087956 wasted 400k of heap here). synchronized(mScanResultCache) { mScanResults = new ArrayList<ScanResult>(); String[] lines = scanResults.split("\n"); @@ -2307,9 +2381,7 @@ public class WifiStateMachine extends StateMachine { mDhcpActive = false; - if (mBatchedScanSettings != null) { - startBatchedScan(); - } + startBatchedScan(); } private void handleSuccessfulIpConfiguration(DhcpResults dhcpResults) { @@ -2443,11 +2515,13 @@ public class WifiStateMachine extends StateMachine { // to the driver happened between mPersistedCountryCode getting set // and now, so simply persisting it here would mean we have sent // nothing to the driver. Send the cmd so it might be set now. - sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE, countryCode); + int sequenceNum = mCountryCodeSequence.incrementAndGet(); + sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE, + sequenceNum, 0, countryCode); } break; case CMD_SET_BATCHED_SCAN: - recordBatchedScanSettings((BatchedScanSettings)message.obj); + recordBatchedScanSettings(message.arg1, message.arg2, (Bundle)message.obj); break; case CMD_POLL_BATCHED_SCAN: handleBatchedScanPollRequest(); @@ -2960,9 +3034,7 @@ public class WifiStateMachine extends StateMachine { mDhcpActive = false; - if (mBatchedScanSettings != null) { - startBatchedScan(); - } + startBatchedScan(); if (mOperationalMode != CONNECT_MODE) { mWifiNative.disconnect(); @@ -3021,23 +3093,40 @@ public class WifiStateMachine extends StateMachine { startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP); break; case CMD_SET_BATCHED_SCAN: - recordBatchedScanSettings((BatchedScanSettings)message.obj); - startBatchedScan(); + if (recordBatchedScanSettings(message.arg1, message.arg2, + (Bundle)message.obj)) { + if (mBatchedScanSettings != null) { + startBatchedScan(); + } else { + stopBatchedScan(); + } + } break; case CMD_SET_COUNTRY_CODE: String country = (String) message.obj; + final boolean persist = (message.arg2 == 1); + final int sequence = message.arg1; + if (sequence != mCountryCodeSequence.get()) { + if (DBG) log("set country code ignored due to sequence num"); + break; + } if (DBG) log("set country code " + country); - if (country != null) { - country = country.toUpperCase(Locale.ROOT); - if (mLastSetCountryCode == null - || country.equals(mLastSetCountryCode) == false) { - if (mWifiNative.setCountryCode(country)) { - mLastSetCountryCode = country; - } else { - loge("Failed to set country code " + country); - } + if (persist) { + mPersistedCountryCode = country; + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.WIFI_COUNTRY_CODE, + country); + } + country = country.toUpperCase(Locale.ROOT); + if (mLastSetCountryCode == null + || country.equals(mLastSetCountryCode) == false) { + if (mWifiNative.setCountryCode(country)) { + mLastSetCountryCode = country; + } else { + loge("Failed to set country code " + country); } } + mWifiP2pChannel.sendMessage(WifiP2pService.SET_COUNTRY_CODE, country); break; case CMD_SET_FREQUENCY_BAND: int band = message.arg1; @@ -3160,9 +3249,7 @@ public class WifiStateMachine extends StateMachine { updateBatteryWorkSource(null); mScanResults = new ArrayList<ScanResult>(); - if (mBatchedScanSettings != null) { - stopBatchedScan(); - } + stopBatchedScan(); final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); |