diff options
122 files changed, 3966 insertions, 1278 deletions
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 9709299fd16a..9185d7ac127a 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -2266,12 +2266,12 @@ public class Am extends BaseCommand { System.err.println("Error: invalid input bounds"); return; } - taskResize(taskId, bounds, 0); + taskResize(taskId, bounds, 0, false); } - private void taskResize(int taskId, Rect bounds, int delay_ms) { + private void taskResize(int taskId, Rect bounds, int delay_ms, boolean pretendUserResize) { try { - mAm.resizeTask(taskId, bounds); + mAm.resizeTask(taskId, bounds, pretendUserResize); Thread.sleep(delay_ms); } catch (RemoteException e) { System.err.println("Error changing task bounds: " + e); @@ -2354,7 +2354,7 @@ public class Am extends BaseCommand { taskRect.top += maxMove; taskRect.bottom += maxMove; } - taskResize(taskId, taskRect, delay_ms); + taskResize(taskId, taskRect, delay_ms, false); } } else { while (maxToTravel < 0 @@ -2371,7 +2371,7 @@ public class Am extends BaseCommand { taskRect.top -= maxMove; taskRect.bottom -= maxMove; } - taskResize(taskId, taskRect, delay_ms); + taskResize(taskId, taskRect, delay_ms, false); } } // Return the remaining distance we didn't travel because we reached the target location. @@ -2405,7 +2405,7 @@ public class Am extends BaseCommand { currentTaskBounds.left -= getStepSize( currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET); - taskResize(taskId, currentTaskBounds, delay_ms); + taskResize(taskId, currentTaskBounds, delay_ms, true); } while (stackBounds.top < currentTaskBounds.top || stackBounds.left < currentTaskBounds.left); @@ -2418,7 +2418,7 @@ public class Am extends BaseCommand { currentTaskBounds.left += getStepSize( currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET); - taskResize(taskId, currentTaskBounds, delay_ms); + taskResize(taskId, currentTaskBounds, delay_ms, true); } while (initialTaskBounds.top > currentTaskBounds.top || initialTaskBounds.left > currentTaskBounds.left); @@ -2431,7 +2431,7 @@ public class Am extends BaseCommand { currentTaskBounds.right += getStepSize( currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET); - taskResize(taskId, currentTaskBounds, delay_ms); + taskResize(taskId, currentTaskBounds, delay_ms, true); } while (stackBounds.top < currentTaskBounds.top || stackBounds.right > currentTaskBounds.right); @@ -2444,7 +2444,7 @@ public class Am extends BaseCommand { currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right, stepSize, GREATER_THAN_TARGET); - taskResize(taskId, currentTaskBounds, delay_ms); + taskResize(taskId, currentTaskBounds, delay_ms, true); } while (initialTaskBounds.top > currentTaskBounds.top || initialTaskBounds.right < currentTaskBounds.right); @@ -2457,7 +2457,7 @@ public class Am extends BaseCommand { currentTaskBounds.left -= getStepSize( currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET); - taskResize(taskId, currentTaskBounds, delay_ms); + taskResize(taskId, currentTaskBounds, delay_ms, true); } while (stackBounds.bottom > currentTaskBounds.bottom || stackBounds.left < currentTaskBounds.left); @@ -2470,7 +2470,7 @@ public class Am extends BaseCommand { currentTaskBounds.left += getStepSize( currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET); - taskResize(taskId, currentTaskBounds, delay_ms); + taskResize(taskId, currentTaskBounds, delay_ms, true); } while (initialTaskBounds.bottom < currentTaskBounds.bottom || initialTaskBounds.left > currentTaskBounds.left); @@ -2483,7 +2483,7 @@ public class Am extends BaseCommand { currentTaskBounds.right += getStepSize( currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET); - taskResize(taskId, currentTaskBounds, delay_ms); + taskResize(taskId, currentTaskBounds, delay_ms, true); } while (stackBounds.bottom > currentTaskBounds.bottom || stackBounds.right > currentTaskBounds.right); @@ -2496,7 +2496,7 @@ public class Am extends BaseCommand { currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right, stepSize, GREATER_THAN_TARGET); - taskResize(taskId, currentTaskBounds, delay_ms); + taskResize(taskId, currentTaskBounds, delay_ms, true); } while (initialTaskBounds.bottom < currentTaskBounds.bottom || initialTaskBounds.right < currentTaskBounds.right); } diff --git a/cmds/svc/src/com/android/commands/svc/NfcCommand.java b/cmds/svc/src/com/android/commands/svc/NfcCommand.java new file mode 100644 index 000000000000..e0f09ee2c666 --- /dev/null +++ b/cmds/svc/src/com/android/commands/svc/NfcCommand.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.svc; + +import android.content.Context; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.nfc.INfcAdapter; +import android.os.RemoteException; +import android.os.ServiceManager; + +public class NfcCommand extends Svc.Command { + + public NfcCommand() { + super("nfc"); + } + + @Override + public String shortHelp() { + return "Control NFC functions"; + } + + @Override + public String longHelp() { + return shortHelp() + "\n" + + "\n" + + "usage: svc nfc [enable|disable]\n" + + " Turn NFC on or off.\n\n"; + } + + @Override + public void run(String[] args) { + boolean validCommand = false; + if (args.length >= 2) { + boolean flag = false; + if ("enable".equals(args[1])) { + flag = true; + validCommand = true; + } else if ("disable".equals(args[1])) { + flag = false; + validCommand = true; + } + if (validCommand) { + IPackageManager pm = IPackageManager.Stub.asInterface( + ServiceManager.getService("package")); + try { + if (pm.hasSystemFeature(PackageManager.FEATURE_NFC)) { + INfcAdapter nfc = INfcAdapter.Stub + .asInterface(ServiceManager.getService(Context.NFC_SERVICE)); + try { + if (flag) { + nfc.enable(); + } else + nfc.disable(true); + } catch (RemoteException e) { + System.err.println("NFC operation failed: " + e); + } + } else { + System.err.println("NFC feature not supported."); + } + } catch (RemoteException e) { + System.err.println("RemoteException while calling PackageManager, is the " + + "system running?"); + } + return; + } + } + System.err.println(longHelp()); + } + +} diff --git a/cmds/svc/src/com/android/commands/svc/Svc.java b/cmds/svc/src/com/android/commands/svc/Svc.java index 0fbba11e927d..2cccd1a4dc92 100644 --- a/cmds/svc/src/com/android/commands/svc/Svc.java +++ b/cmds/svc/src/com/android/commands/svc/Svc.java @@ -95,6 +95,7 @@ public class Svc { new PowerCommand(), new DataCommand(), new WifiCommand(), - new UsbCommand() + new UsbCommand(), + new NfcCommand(), }; } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f7dcf02da0a5..4997dc751082 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4879,7 +4879,8 @@ public class Activity extends ContextThemeWrapper if (Looper.myLooper() != mMainThread.getLooper()) { throw new IllegalStateException("Must be called from main thread"); } - mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, null, false); + mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, null, false, + false /* preserveWindow */); } /** @@ -6223,12 +6224,13 @@ public class Activity extends ContextThemeWrapper Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, - Configuration config, String referrer, IVoiceInteractor voiceInteractor) { + Configuration config, String referrer, IVoiceInteractor voiceInteractor, + Window window) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); - mWindow = new PhoneWindow(this); + mWindow = new PhoneWindow(this, window); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); @@ -6317,11 +6319,15 @@ public class Activity extends ContextThemeWrapper final void performRestart() { mFragments.noteStateNotSaved(); + if (mToken != null && mParent == null) { + // We might have view roots that were preserved during a relaunch, we need to start them + // again. We don't need to check mStopped, the roots will check if they were actually + // stopped. + WindowManagerGlobal.getInstance().setStoppedState(mToken, false /* stopped */); + } + if (mStopped) { mStopped = false; - if (mToken != null && mParent == null) { - WindowManagerGlobal.getInstance().setStoppedState(mToken, false); - } synchronized (mManagedCursors) { final int N = mManagedCursors.size(); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index eeae20fa1ccf..9ef51c8554d3 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -452,6 +452,22 @@ public class ActivityManager { */ public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1; + /** + * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which + * specifies the position of the created docked stack at the top half of the screen if + * in portrait mode or at the left half of the screen if in landscape mode. + * @hide + */ + public static final int DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT = 0; + + /** + * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which + * specifies the position of the created docked stack at the bottom half of the screen if + * in portrait mode or at the right half of the screen if in landscape mode. + * @hide + */ + public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1; + /** @hide */ public int getFrontActivityScreenCompatMode() { try { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index d1c73bc945ff..da6fc592e69a 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -743,6 +743,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case MOVE_TASK_TO_DOCKED_STACK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int taskId = data.readInt(); + int createMode = data.readInt(); + boolean toTop = data.readInt() != 0; + moveTaskToDockedStack(taskId, createMode, toTop); + reply.writeNoException(); + return true; + } + case RESIZE_STACK_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int stackId = data.readInt(); @@ -2442,8 +2452,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case RESIZE_TASK_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int taskId = data.readInt(); + final boolean resizedByUser = data.readInt() == 1; Rect r = Rect.CREATOR.createFromParcel(data); - resizeTask(taskId, r); + resizeTask(taskId, r, resizedByUser); reply.writeNoException(); return true; } @@ -3510,6 +3521,21 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } @Override + public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop) + throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(taskId); + data.writeInt(createMode); + data.writeInt(toTop ? 1 : 0); + mRemote.transact(MOVE_TASK_TO_DOCKED_STACK_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + @Override public void resizeStack(int stackId, Rect r) throws RemoteException { Parcel data = Parcel.obtain(); @@ -5874,12 +5900,13 @@ class ActivityManagerProxy implements IActivityManager } @Override - public void resizeTask(int taskId, Rect r) throws RemoteException + public void resizeTask(int taskId, Rect r, boolean resizedByUser) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(taskId); + data.writeInt(resizedByUser ? 1 : 0); r.writeToParcel(data, 0); mRemote.transact(RESIZE_TASK_TRANSACTION, data, reply, 0); reply.readException(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 67dee7f43c32..4b8efab3e59e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -315,8 +315,9 @@ public final class ActivityThread { int pendingConfigChanges; boolean onlyLocalRequest; - View mPendingRemoveWindow; + Window mPendingRemoveWindow; WindowManager mPendingRemoveWindowManager; + boolean mPreserveWindow; ActivityClientRecord() { parent = null; @@ -670,9 +671,9 @@ public final class ActivityThread { public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed, Configuration config, - Configuration overrideConfig) { + Configuration overrideConfig, boolean preserveWindow) { requestRelaunchActivity(token, pendingResults, pendingNewIntents, - configChanges, notResumed, config, overrideConfig, true); + configChanges, notResumed, config, overrideConfig, true, preserveWindow); } public final void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token) { @@ -2376,10 +2377,16 @@ public final class ActivityThread { Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); + Window window = null; + if (r.mPendingRemoveWindow != null && r.mPreserveWindow) { + window = r.mPendingRemoveWindow; + r.mPendingRemoveWindow = null; + r.mPendingRemoveWindowManager = null; + } activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, - r.referrer, r.voiceInteractor); + r.referrer, r.voiceInteractor, window); if (customIntent != null) { activity.mIntent = customIntent; @@ -3191,10 +3198,14 @@ public final class ActivityThread { return r; } - static final void cleanUpPendingRemoveWindows(ActivityClientRecord r) { + static final void cleanUpPendingRemoveWindows(ActivityClientRecord r, boolean force) { + if (r.mPreserveWindow && !force) { + return; + } if (r.mPendingRemoveWindow != null) { - r.mPendingRemoveWindowManager.removeViewImmediate(r.mPendingRemoveWindow); - IBinder wtoken = r.mPendingRemoveWindow.getWindowToken(); + r.mPendingRemoveWindowManager.removeViewImmediate( + r.mPendingRemoveWindow.getDecorView()); + IBinder wtoken = r.mPendingRemoveWindow.getDecorView().getWindowToken(); if (wtoken != null) { WindowManagerGlobal.getInstance().closeAll(wtoken, r.activity.getClass().getName(), "Activity"); @@ -3245,7 +3256,11 @@ public final class ActivityThread { a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; - if (a.mVisibleFromClient) { + if (r.mPreserveWindow) { + a.mWindowAdded = true; + r.mPreserveWindow = false; + } + if (a.mVisibleFromClient && !a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } @@ -3260,7 +3275,7 @@ public final class ActivityThread { } // Get rid of anything left hanging around. - cleanUpPendingRemoveWindows(r); + cleanUpPendingRemoveWindows(r, false /* force */); // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. @@ -3745,7 +3760,8 @@ public final class ActivityThread { // request all activities to relaunch for the changes to take place for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) { - requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, null, false); + requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, null, false, + false /* preserveWindow */); } } } @@ -3931,7 +3947,7 @@ public final class ActivityThread { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance); if (r != null) { - cleanUpPendingRemoveWindows(r); + cleanUpPendingRemoveWindows(r, finishing); WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; if (v != null) { @@ -3940,11 +3956,18 @@ public final class ActivityThread { } IBinder wtoken = v.getWindowToken(); if (r.activity.mWindowAdded) { - if (r.onlyLocalRequest) { + boolean reuseForResize = r.window.hasNonClientDecorView() && r.mPreserveWindow; + if (r.onlyLocalRequest || reuseForResize) { // Hold off on removing this until the new activity's // window is being added. - r.mPendingRemoveWindow = v; + r.mPendingRemoveWindow = r.window; r.mPendingRemoveWindowManager = wm; + if (reuseForResize) { + // We can only keep the part of the view hierarchy that we control, + // everything else must be removed, because it might not be able to + // behave properly when activity is relaunching. + r.window.clearContentView(); + } } else { wm.removeViewImmediate(v); } @@ -3986,10 +4009,14 @@ public final class ActivityThread { mSomeActivitiesChanged = true; } + /** + * @param preserveWindow Whether the activity should try to reuse the window it created, + * including the decor view after the relaunch. + */ public final void requestRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed, Configuration config, - Configuration overrideConfig, boolean fromServer) { + Configuration overrideConfig, boolean fromServer, boolean preserveWindow) { ActivityClientRecord target = null; synchronized (mResourcesManager) { @@ -4020,6 +4047,7 @@ public final class ActivityThread { target.token = token; target.pendingResults = pendingResults; target.pendingIntents = pendingNewIntents; + target.mPreserveWindow = preserveWindow; if (!fromServer) { ActivityClientRecord existing = mActivities.get(token); if (existing != null) { @@ -4120,6 +4148,7 @@ public final class ActivityThread { r.activity.mConfigChangeFlags |= configChanges; r.onlyLocalRequest = tmp.onlyLocalRequest; + r.mPreserveWindow = tmp.mPreserveWindow; Intent currentIntent = r.activity.mIntent; r.activity.mChangingConfigurations = true; diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index f164a0a76d8f..bead625a0bc1 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -63,14 +63,14 @@ public abstract class ApplicationThreadNative extends Binder if (in != null) { return in; } - + return new ApplicationThreadProxy(obj); } - + public ApplicationThreadNative() { attachInterface(this, descriptor); } - + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -96,7 +96,7 @@ public abstract class ApplicationThreadNative extends Binder scheduleStopActivity(b, show, configChanges); return true; } - + case SCHEDULE_WINDOW_VISIBILITY_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -125,7 +125,7 @@ public abstract class ApplicationThreadNative extends Binder scheduleResumeActivity(b, procState, isForward, resumeArgs); return true; } - + case SCHEDULE_SEND_RESULT_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -179,7 +179,9 @@ public abstract class ApplicationThreadNative extends Binder if (data.readInt() != 0) { overrideConfig = Configuration.CREATOR.createFromParcel(data); } - scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config, overrideConfig); + boolean preserveWindows = data.readInt() == 1; + scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config, overrideConfig, + preserveWindows); return true; } @@ -201,7 +203,7 @@ public abstract class ApplicationThreadNative extends Binder scheduleDestroyActivity(b, finishing, configChanges); return true; } - + case SCHEDULE_RECEIVER_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -371,7 +373,7 @@ public abstract class ApplicationThreadNative extends Binder } return true; } - + case DUMP_PROVIDER_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); ParcelFileDescriptor fd = data.readFileDescriptor(); @@ -731,15 +733,15 @@ public abstract class ApplicationThreadNative extends Binder class ApplicationThreadProxy implements IApplicationThread { private final IBinder mRemote; - + public ApplicationThreadProxy(IBinder remote) { mRemote = remote; } - + public final IBinder asBinder() { return mRemote; } - + public final void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, boolean dontReport) throws RemoteException { Parcel data = Parcel.obtain(); @@ -856,7 +858,7 @@ class ApplicationThreadProxy implements IApplicationThread { public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed, Configuration config, - Configuration overrideConfig) throws RemoteException { + Configuration overrideConfig, boolean preserveWindow) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); @@ -871,6 +873,7 @@ class ApplicationThreadProxy implements IApplicationThread { } else { data.writeInt(0); } + data.writeInt(preserveWindow ? 1 : 0); mRemote.transact(SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); @@ -898,7 +901,7 @@ class ApplicationThreadProxy implements IApplicationThread { IBinder.FLAG_ONEWAY); data.recycle(); } - + public final void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String resultData, Bundle map, boolean sync, int sendingUser, int processState) throws RemoteException { @@ -940,7 +943,7 @@ class ApplicationThreadProxy implements IApplicationThread { IBinder.FLAG_ONEWAY); data.recycle(); } - + public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) throws RemoteException { Parcel data = Parcel.obtain(); @@ -1055,7 +1058,7 @@ class ApplicationThreadProxy implements IApplicationThread { IBinder.FLAG_ONEWAY); data.recycle(); } - + public final void scheduleExit() throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); @@ -1128,7 +1131,7 @@ class ApplicationThreadProxy implements IApplicationThread { mRemote.transact(DUMP_SERVICE_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); } - + public void dumpProvider(FileDescriptor fd, IBinder token, String[] args) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 66fa79639256..7bd832bf5f95 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -140,6 +140,8 @@ public interface IActivityManager extends IInterface { public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException; public void moveTaskBackwards(int task) throws RemoteException; public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException; + public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop) + throws RemoteException; public void resizeStack(int stackId, Rect bounds) throws RemoteException; public void positionTaskInStack(int taskId, int stackId, int position) throws RemoteException; public List<StackInfo> getAllStackInfos() throws RemoteException; @@ -489,7 +491,7 @@ public interface IActivityManager extends IInterface { public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values) throws RemoteException; public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException; - public void resizeTask(int taskId, Rect bounds) throws RemoteException; + public void resizeTask(int taskId, Rect bounds, boolean resizedByUser) throws RemoteException; public Rect getTaskBounds(int taskId) throws RemoteException; public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException; @@ -888,4 +890,5 @@ public interface IActivityManager extends IInterface { int GET_ACTIVITY_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 343; int MOVE_ACTIVITY_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 344; int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345; + int MOVE_TASK_TO_DOCKED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index dc8f53d94b4e..2d78e19750ae 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -65,7 +65,8 @@ public interface IApplicationThread extends IInterface { boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) throws RemoteException; void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed, - Configuration config, Configuration overrideConfig) throws RemoteException; + Configuration config, Configuration overrideConfig, boolean preserveWindow) + throws RemoteException; void scheduleNewIntent(List<ReferrerIntent> intent, IBinder token) throws RemoteException; void scheduleDestroyActivity(IBinder token, boolean finished, int configChanges) throws RemoteException; diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index dee8d2115863..718433719d0d 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1044,7 +1044,7 @@ public class Instrumentation { activity.attach(context, aThread, this, token, 0, application, intent, info, title, parent, id, (Activity.NonConfigurationInstances)lastNonConfigurationInstance, - new Configuration(), null, null); + new Configuration(), null, null, null); return activity; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e6590493616b..b08db20c2eea 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1613,6 +1613,23 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.action.GET_PERMISSIONS_COUNT"; /** + * Broadcast action that requests list of all apps that have runtime permissions. It will + * respond to the request by sending a broadcast with action defined by + * {@link #EXTRA_GET_PERMISSIONS_PACKAGES_RESPONSE_INTENT}. The response will contain + * {@link #EXTRA_GET_PERMISSIONS_APP_LIST_RESULT}, as well as + * {@link #EXTRA_GET_PERMISSIONS_APP_LABEL_LIST_RESULT}, with contents described below or + * a null upon failure. + * + * <p>{@link #EXTRA_GET_PERMISSIONS_APP_LIST_RESULT} will contain a list of package names of + * apps that have runtime permissions. {@link #EXTRA_GET_PERMISSIONS_APP_LABEL_LIST_RESULT} + * will contain the list of app labels corresponding ot the apps in the first list. + * + * @hide + */ + public static final String ACTION_GET_PERMISSIONS_PACKAGES + = "android.intent.action.GET_PERMISSIONS_PACKAGES"; + + /** * Extra included in response to {@link #ACTION_GET_PERMISSIONS_COUNT}. * @hide */ @@ -1627,6 +1644,20 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.extra.GET_PERMISSIONS_GROUP_LIST_RESULT"; /** + * String list of apps that have one or more runtime permissions. + * @hide + */ + public static final String EXTRA_GET_PERMISSIONS_APP_LIST_RESULT + = "android.intent.extra.GET_PERMISSIONS_APP_LIST_RESULT"; + + /** + * String list of app labels for apps that have one or more runtime permissions. + * @hide + */ + public static final String EXTRA_GET_PERMISSIONS_APP_LABEL_LIST_RESULT + = "android.intent.extra.GET_PERMISSIONS_APP_LABEL_LIST_RESULT"; + + /** * Required extra to be sent with {@link #ACTION_GET_PERMISSIONS_COUNT} broadcasts. * @hide */ @@ -1634,6 +1665,13 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.extra.GET_PERMISSIONS_RESONSE_INTENT"; /** + * Required extra to be sent with {@link #ACTION_GET_PERMISSIONS_PACKAGES} broadcasts. + * @hide + */ + public static final String EXTRA_GET_PERMISSIONS_PACKAGES_RESPONSE_INTENT + = "android.intent.extra.GET_PERMISSIONS_PACKAGES_RESONSE_INTENT"; + + /** * Activity action: Launch UI to manage which apps have a given permission. * <p> * Input: {@link #EXTRA_PERMISSION_NAME} specifies the permission access diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index a59f429bfabc..a121b4d312fd 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -33,12 +33,16 @@ import java.lang.annotation.RetentionPolicy; */ public class ActivityInfo extends ComponentInfo implements Parcelable { + + // NOTE: When adding new data members be sure to update the copy-constructor, Parcel + // constructor, and writeToParcel. + /** * A style resource identifier (in the package's resources) of this * activity's theme. From the "theme" attribute or, if not set, 0. */ public int theme; - + /** * Constant corresponding to <code>standard</code> in * the {@link android.R.attr#launchMode} attribute. @@ -707,6 +711,7 @@ public class ActivityInfo extends ComponentInfo super(orig); theme = orig.theme; launchMode = orig.launchMode; + documentLaunchMode = orig.documentLaunchMode; permission = orig.permission; taskAffinity = orig.taskAffinity; targetActivity = orig.targetActivity; @@ -788,6 +793,7 @@ public class ActivityInfo extends ComponentInfo super.writeToParcel(dest, parcelableFlags); dest.writeInt(theme); dest.writeInt(launchMode); + dest.writeInt(documentLaunchMode); dest.writeString(permission); dest.writeString(taskAffinity); dest.writeString(targetActivity); @@ -827,6 +833,7 @@ public class ActivityInfo extends ComponentInfo super(source); theme = source.readInt(); launchMode = source.readInt(); + documentLaunchMode = source.readInt(); permission = source.readString(); taskAffinity = source.readString(); targetActivity = source.readString(); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 968f9b27d629..d7ecbfeb80eb 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -4561,6 +4561,17 @@ public class PackageParser { return applicationInfo.isUpdatedSystemApp(); } + /** + * @hide + */ + public boolean canHaveOatDir() { + // The following app types CANNOT have oat directory + // - non-updated system apps + // - forward-locked apps or apps installed in ASEC containers + return (!isSystemApp() || isUpdatedSystemApp()) + && !isForwardLocked() && !applicationInfo.isExternalAsec(); + } + public String toString() { return "Package{" + Integer.toHexString(System.identityHashCode(this)) diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 7fef5e17c5cb..04caa8fcda38 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -772,10 +772,10 @@ public class FingerprintManager { if (mRemovalCallback != null) { int reqFingerId = mRemovalFingerprint.getFingerId(); int reqGroupId = mRemovalFingerprint.getGroupId(); - if (fingerId != reqFingerId) { + if (reqFingerId != 0 && fingerId != reqFingerId) { Log.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId); } - if (fingerId != reqFingerId) { + if (groupId != reqGroupId) { Log.w(TAG, "Group id didn't match: " + groupId + " != " + reqGroupId); } mRemovalCallback.onRemovalSucceeded(mRemovalFingerprint); @@ -962,4 +962,3 @@ public class FingerprintManager { }; } - diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 48ede4f01585..213e0831c0f2 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -57,15 +57,15 @@ public final class UserHandle implements Parcelable { /** * @hide A user id constant to indicate the "owner" user of the device - * @deprecated Consider using either USER_SYSTEM constant or - * UserInfo.isPrimary(). + * @deprecated Consider using either {@link UserHandle#USER_SYSTEM} constant or + * check the target user's flag {@link android.content.pm.UserInfo#isAdmin}. */ public static final int USER_OWNER = 0; /** * @hide A user handle to indicate the primary/owner user of the device - * @deprecated Consider using either SYSTEM constant or - * UserInfo.isPrimary(). + * @deprecated Consider using either {@link UserHandle#SYSTEM} constant or + * check the target user's flag {@link android.content.pm.UserInfo#isAdmin}. */ public static final UserHandle OWNER = new UserHandle(USER_OWNER); @@ -90,7 +90,7 @@ public final class UserHandle implements Parcelable { * user. * @hide */ - public static final boolean isSameUser(int uid1, int uid2) { + public static boolean isSameUser(int uid1, int uid2) { return getUserId(uid1) == getUserId(uid2); } @@ -102,12 +102,12 @@ public final class UserHandle implements Parcelable { * @return whether the appId is the same for both uids * @hide */ - public static final boolean isSameApp(int uid1, int uid2) { + public static boolean isSameApp(int uid1, int uid2) { return getAppId(uid1) == getAppId(uid2); } /** @hide */ - public static final boolean isIsolated(int uid) { + public static boolean isIsolated(int uid) { if (uid > 0) { final int appId = getAppId(uid); return appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID; @@ -130,7 +130,7 @@ public final class UserHandle implements Parcelable { * Returns the user id for a given uid. * @hide */ - public static final int getUserId(int uid) { + public static int getUserId(int uid) { if (MU_ENABLED) { return uid / PER_USER_RANGE; } else { @@ -139,12 +139,12 @@ public final class UserHandle implements Parcelable { } /** @hide */ - public static final int getCallingUserId() { + public static int getCallingUserId() { return getUserId(Binder.getCallingUid()); } /** @hide */ - public static final UserHandle getCallingUserHandle() { + public static UserHandle getCallingUserHandle() { int userId = getUserId(Binder.getCallingUid()); UserHandle userHandle = userHandles.get(userId); // Intentionally not synchronized to save time @@ -159,7 +159,7 @@ public final class UserHandle implements Parcelable { * Returns the uid that is composed from the userId and the appId. * @hide */ - public static final int getUid(int userId, int appId) { + public static int getUid(int userId, int appId) { if (MU_ENABLED) { return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); } else { @@ -171,7 +171,7 @@ public final class UserHandle implements Parcelable { * Returns the app id (or base uid) for a given uid, stripping out the user id from it. * @hide */ - public static final int getAppId(int uid) { + public static int getAppId(int uid) { return uid % PER_USER_RANGE; } @@ -179,7 +179,7 @@ public final class UserHandle implements Parcelable { * Returns the gid shared between all apps with this userId. * @hide */ - public static final int getUserGid(int userId) { + public static int getUserGid(int userId) { return getUid(userId, Process.SHARED_USER_GID); } @@ -187,7 +187,7 @@ public final class UserHandle implements Parcelable { * Returns the shared app gid for a given uid or appId. * @hide */ - public static final int getSharedAppGid(int id) { + public static int getSharedAppGid(int id) { return Process.FIRST_SHARED_APPLICATION_GID + (id % PER_USER_RANGE) - Process.FIRST_APPLICATION_UID; } @@ -196,7 +196,7 @@ public final class UserHandle implements Parcelable { * Returns the app id for a given shared app gid. Returns -1 if the ID is invalid. * @hide */ - public static final int getAppIdFromSharedAppGid(int gid) { + public static int getAppIdFromSharedAppGid(int gid) { final int appId = getAppId(gid) + Process.FIRST_APPLICATION_UID - Process.FIRST_SHARED_APPLICATION_GID; if (appId < 0 || appId >= Process.FIRST_SHARED_APPLICATION_GID) { @@ -272,7 +272,7 @@ public final class UserHandle implements Parcelable { * @hide */ @SystemApi - public static final int myUserId() { + public static int myUserId() { return getUserId(Process.myUid()); } @@ -280,9 +280,10 @@ public final class UserHandle implements Parcelable { * Returns true if this UserHandle refers to the owner user; false otherwise. * @return true if this UserHandle refers to the owner user; false otherwise. * @hide + * TODO: find an alternative to this Api. */ @SystemApi - public final boolean isOwner() { + public boolean isOwner() { return this.equals(OWNER); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a038b0d30887..225f0cf97c2e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3283,7 +3283,6 @@ public final class Settings { DOCK_SOUNDS_ENABLED, // moved to global LOCKSCREEN_SOUNDS_ENABLED, SHOW_WEB_SUGGESTIONS, - NOTIFICATION_LIGHT_PULSE, SIP_CALL_OPTIONS, SIP_RECEIVE_CALLS, POINTER_SPEED, @@ -4921,7 +4920,26 @@ public final class Settings { "accessibility_display_daltonizer"; /** - * The timout for considering a press to be a long press in milliseconds. + * Setting that specifies whether automatic click when the mouse pointer stops moving is + * enabled. + * + * @hide + */ + public static final String ACCESSIBILITY_AUTOCLICK_ENABLED = + "accessibility_autoclick_enabled"; + + /** + * Integer setting specifying amount of time in ms the mouse pointer has to stay still + * before performing click when {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set. + * + * @see #ACCESSIBILITY_AUTOCLICK_ENABLED + * @hide + */ + public static final String ACCESSIBILITY_AUTOCLICK_DELAY = + "accessibility_autoclick_delay"; + + /** + * The timeout for considering a press to be a long press in milliseconds. * @hide */ public static final String LONG_PRESS_TIMEOUT = "long_press_timeout"; @@ -5785,6 +5803,8 @@ public final class Settings { SLEEP_TIMEOUT, DOUBLE_TAP_TO_WAKE, CAMERA_GESTURE_DISABLED, + ACCESSIBILITY_AUTOCLICK_ENABLED, + ACCESSIBILITY_AUTOCLICK_DELAY }; /** diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index b146a51292e1..0e7089ff7a8c 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1175,6 +1175,13 @@ public abstract class Window { public abstract void addContentView(View view, ViewGroup.LayoutParams params); /** + * Remove the view that was used as the screen content. + * + * @hide + */ + public abstract void clearContentView(); + + /** * Return the view in this Window that currently has focus, or null if * there are none. Note that this does not look in any containing * Window. @@ -1239,6 +1246,15 @@ public abstract class Window { public void setElevation(float elevation) {} /** + * Gets the window elevation. + * + * @hide + */ + public float getElevation() { + return 0.0f; + } + + /** * Sets whether window content should be clipped to the outline of the * window background. * @@ -1991,5 +2007,13 @@ public abstract class Window { */ public abstract void setNavigationBarColor(@ColorInt int color); - + /** + * Get information whether the activity has non client decoration view. These views are used in + * the multi window environment, to provide dragging handle and maximize/close buttons. + * + * @hide + */ + public boolean hasNonClientDecorView() { + return false; + } } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 057b7010999a..d0c50c9309c2 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -962,6 +962,9 @@ public class WebView extends AbsoluteLayout * If the base URL uses any other scheme, then the data will be loaded into * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded * entities in the string will not be decoded. + * <p> + * Note that the baseUrl is sent in the 'Referer' HTTP header when + * requesting subresources (images, etc.) of the page loaded using this method. * * @param baseUrl the URL to use as the page's base URL. If null defaults to * 'about:blank'. diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index 2cfefba10c57..6ed7ab8fde68 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -815,6 +815,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { @Override protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { dispatchThawSelfOnly(container); + handleDataChanged(); } class AdapterDataSetObserver extends DataSetObserver { diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java index bcbafc9af158..c869ccbdef58 100644 --- a/core/java/android/widget/DropDownListView.java +++ b/core/java/android/widget/DropDownListView.java @@ -132,11 +132,6 @@ public class DropDownListView extends ListView { return selectedView != null && selectedView.isEnabled() || super.shouldShowSelector(); } - protected void clearSelection() { - setSelectedPositionInt(-1); - setNextSelectedPositionInt(-1); - } - @Override public boolean onHoverEvent(MotionEvent ev) { final int action = ev.getActionMasked(); diff --git a/core/java/android/widget/MenuItemHoverListener.java b/core/java/android/widget/MenuItemHoverListener.java new file mode 100644 index 000000000000..87c5c852973e --- /dev/null +++ b/core/java/android/widget/MenuItemHoverListener.java @@ -0,0 +1,13 @@ +package android.widget; + +import com.android.internal.view.menu.MenuBuilder; + +/** + * An interface notified when a menu item is hovered. Useful for cases when hover should trigger + * some behavior at a higher level, like managing the opening and closing of submenus. + * + * @hide + */ +public interface MenuItemHoverListener { + public void onItemHovered(MenuBuilder menu, int position); +} diff --git a/core/java/android/widget/MenuPopupWindow.java b/core/java/android/widget/MenuPopupWindow.java index 900aa326d502..ba77b1b53079 100644 --- a/core/java/android/widget/MenuPopupWindow.java +++ b/core/java/android/widget/MenuPopupWindow.java @@ -22,12 +22,12 @@ import android.content.res.Resources; import android.transition.Transition; import android.util.AttributeSet; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; import com.android.internal.view.menu.ListMenuItemView; import com.android.internal.view.menu.MenuAdapter; +import com.android.internal.view.menu.MenuBuilder; /** * A MenuPopupWindow represents the popup window for menu. @@ -37,20 +37,32 @@ import com.android.internal.view.menu.MenuAdapter; * * @hide */ -public class MenuPopupWindow extends ListPopupWindow { +public class MenuPopupWindow extends ListPopupWindow implements MenuItemHoverListener { + private MenuItemHoverListener mHoverListener; + public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override DropDownListView createDropDownListView(Context context, boolean hijackFocus) { - return new MenuDropDownListView(context, hijackFocus); + MenuDropDownListView view = new MenuDropDownListView(context, hijackFocus); + view.setHoverListener(this); + return view; } public void setEnterTransition(Transition enterTransition) { mPopup.setEnterTransition(enterTransition); } + public void setExitTransition(Transition exitTransition) { + mPopup.setExitTransition(exitTransition); + } + + public void setHoverListener(MenuItemHoverListener hoverListener) { + mHoverListener = hoverListener; + } + /** * Set whether this window is touch modal or if outside touches will be sent to * other windows behind it. @@ -59,10 +71,23 @@ public class MenuPopupWindow extends ListPopupWindow { mPopup.setTouchModal(touchModal); } - private static class MenuDropDownListView extends DropDownListView { + @Override + public void onItemHovered(MenuBuilder menu, int position) { + // Forward up the chain + if (mHoverListener != null) { + mHoverListener.onItemHovered(menu, position); + } + } + + /** + * @hide + */ + public static class MenuDropDownListView extends DropDownListView { final int mAdvanceKey; final int mRetreatKey; + private MenuItemHoverListener mHoverListener; + public MenuDropDownListView(Context context, boolean hijackFocus) { super(context, hijackFocus); @@ -77,6 +102,15 @@ public class MenuPopupWindow extends ListPopupWindow { } } + public void setHoverListener(MenuItemHoverListener hoverListener) { + mHoverListener = hoverListener; + } + + public void clearSelection() { + setSelectedPositionInt(INVALID_POSITION); + setNextSelectedPositionInt(INVALID_POSITION); + } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView(); @@ -99,6 +133,32 @@ public class MenuPopupWindow extends ListPopupWindow { return super.onKeyDown(keyCode, event); } + @Override + public boolean onHoverEvent(MotionEvent ev) { + boolean dispatchHover = false; + final int position = pointToPosition((int) ev.getX(), (int) ev.getY()); + + final int action = ev.getActionMasked(); + if (action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE) { + if (position != INVALID_POSITION && position != mSelectedPosition) { + final View hoveredItem = getChildAt(position - getFirstVisiblePosition()); + if (hoveredItem.isEnabled()) { + dispatchHover = true; + } + } + } + + boolean superVal = super.onHoverEvent(ev); + + if (dispatchHover && mHoverListener != null) { + mHoverListener.onItemHovered( + ((MenuAdapter) getAdapter()).getAdapterMenu(), position); + } + + return superVal; + } } + }
\ No newline at end of file diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java index bd1fbb8f29c5..34a843925700 100644 --- a/core/java/android/widget/PopupMenu.java +++ b/core/java/android/widget/PopupMenu.java @@ -270,25 +270,8 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback { * @hide */ public boolean onOpenSubMenu(MenuBuilder subMenu) { - if (subMenu == null) return false; - - if (!subMenu.hasVisibleItems()) { - return true; - } - - if (!mShowCascadingMenus) { - // Current menu will be dismissed by the normal helper, submenu will be shown in its - // place. (If cascading menus are enabled, the cascading implementation will show the - // submenu itself). - new MenuPopupHelper(mContext, subMenu, mAnchor).show(); - } - return true; - } - - /** - * @hide - */ - public void onCloseSubMenu(SubMenuBuilder menu) { + // The menu presenter will handle opening the submenu itself. Nothing to do here. + return false; } /** diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index 2365b4850161..61ef6dc9d231 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -22,7 +22,6 @@ import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.text.SpannableStringBuilder; @@ -32,7 +31,6 @@ import android.text.style.TtsSpan; import android.util.AttributeSet; import android.util.Log; import android.util.StateSet; -import android.util.TypedValue; import android.view.HapticFeedbackConstants; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -66,10 +64,8 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl // Also NOT a real index, just used for keyboard mode. private static final int ENABLE_PICKER_INDEX = 3; - private static final int[] ATTRS_TEXT_COLOR = new int[] { - com.android.internal.R.attr.textColor}; - private static final int[] ATTRS_DISABLED_ALPHA = new int[] { - com.android.internal.R.attr.disabledAlpha}; + private static final int[] ATTRS_TEXT_COLOR = new int[] {R.attr.textColor}; + private static final int[] ATTRS_DISABLED_ALPHA = new int[] {R.attr.disabledAlpha}; // LayoutLib relies on these constants. Change TimePickerClockDelegate_Delegate if // modifying these. diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java index a4ef00afdf02..01ac22e3c4fd 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java +++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java @@ -793,6 +793,24 @@ public class InputMethodUtils { return imeMap; } + @NonNull + public static String buildInputMethodsAndSubtypesString( + @NonNull final ArrayMap<String, ArraySet<String>> map) { + // we want to use the canonical InputMethodSettings implementation, + // so we convert data structures first. + List<Pair<String, ArrayList<String>>> imeMap = new ArrayList<>(4); + for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) { + final String imeName = entry.getKey(); + final ArraySet<String> subtypeSet = entry.getValue(); + final ArrayList<String> subtypes = new ArrayList<>(2); + if (subtypeSet != null) { + subtypes.addAll(subtypeSet); + } + imeMap.add(new Pair<>(imeName, subtypes)); + } + return InputMethodSettings.buildInputMethodsSettingString(imeMap); + } + /** * Utility class for putting and getting settings for InputMethod * TODO: Move all putters and getters of settings to this class. diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 7f01841d8afe..ec414474de70 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -170,6 +170,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // This is the top-level view of the window, containing the window decor. private DecorView mDecor; + // When we reuse decor views, we need to recreate the content root. This happens when the decor + // view is requested, so we need to force the recreating without introducing an infinite loop. + private boolean mForceDecorInstall = false; + // This is the non client decor view for the window, containing the caption and window control // buttons. The visibility of this decor depends on the workspace and the window type. // If the window type does not require such a view, this member might be null. @@ -248,6 +252,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private Drawable mBackgroundDrawable; + private boolean mLoadEleveation = true; private float mElevation; /** Whether window content should be clipped to the background outline. */ @@ -323,6 +328,16 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mLayoutInflater = LayoutInflater.from(context); } + public PhoneWindow(Context context, Window preservedWindow) { + this(context); + if (preservedWindow != null) { + mDecor = (DecorView) preservedWindow.getDecorView(); + mElevation = preservedWindow.getElevation(); + mLoadEleveation = false; + mForceDecorInstall = true; + } + } + @Override public final void setContainer(Window container) { super.setContainer(container); @@ -463,6 +478,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } + public void clearContentView() { + if (mNonClientDecorView.getChildCount() > 1) { + mNonClientDecorView.removeViewAt(1); + } + } + private void transitionTo(Scene scene) { if (mContentScene == null) { scene.enter(); @@ -1396,6 +1417,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override + public float getElevation() { + return mElevation; + } + + @Override public final void setClipToOutline(boolean clipToOutline) { mClipToOutline = clipToOutline; if (mDecor != null) { @@ -1992,7 +2018,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public final View getDecorView() { - if (mDecor == null) { + if (mDecor == null || mForceDecorInstall) { installDecor(); } return mDecor; @@ -2266,7 +2292,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } - private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { + private static final class DecorView extends FrameLayout implements RootViewSurfaceTaker { /* package */int mDefaultOpacity = PixelFormat.OPAQUE; @@ -2336,6 +2362,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private int mRootScrollY = 0; + private PhoneWindow mWindow; + public DecorView(Context context, int featureId) { super(context); mFeatureId = featureId; @@ -2357,7 +2385,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void onDraw(Canvas c) { super.onDraw(c); - mBackgroundFallback.draw(mContentRoot, c, mContentParent); + mBackgroundFallback.draw(mWindow.mContentRoot, c, mWindow.mContentParent); } @Override @@ -2369,7 +2397,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (isDown && (event.getRepeatCount() == 0)) { // First handle chording of panel key: if a panel key is held // but not released, try to execute a shortcut in it. - if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) { + if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) { boolean handled = dispatchKeyShortcutEvent(event); if (handled) { return true; @@ -2378,15 +2406,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // If a panel is open, perform a shortcut on it without the // chorded panel key - if ((mPreparedPanel != null) && mPreparedPanel.isOpen) { - if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) { + if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) { + if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) { return true; } } } - if (!isDestroyed()) { - final Callback cb = getCallback(); + if (!mWindow.isDestroyed()) { + final Callback cb = mWindow.getCallback(); final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event); if (handled) { @@ -2394,28 +2422,28 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } - return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event) - : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event); + return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event) + : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event); } @Override public boolean dispatchKeyShortcutEvent(KeyEvent ev) { // If the panel is already prepared, then perform the shortcut using it. boolean handled; - if (mPreparedPanel != null) { - handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev, + if (mWindow.mPreparedPanel != null) { + handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE); if (handled) { - if (mPreparedPanel != null) { - mPreparedPanel.isHandled = true; + if (mWindow.mPreparedPanel != null) { + mWindow.mPreparedPanel.isHandled = true; } return true; } } // Shortcut not handled by the panel. Dispatch to the view hierarchy. - final Callback cb = getCallback(); - handled = cb != null && !isDestroyed() && mFeatureId < 0 + final Callback cb = mWindow.getCallback(); + handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev); if (handled) { return true; @@ -2425,10 +2453,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // combination such as Control+C. Temporarily prepare the panel then mark it // unprepared again when finished to ensure that the panel will again be prepared // the next time it is shown for real. - PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); - if (st != null && mPreparedPanel == null) { - preparePanel(st, ev); - handled = performPanelShortcut(st, ev.getKeyCode(), ev, + PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false); + if (st != null && mWindow.mPreparedPanel == null) { + mWindow.preparePanel(st, ev); + handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev, Menu.FLAG_PERFORM_NO_CLOSE); st.isPrepared = false; if (handled) { @@ -2440,23 +2468,23 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public boolean dispatchTouchEvent(MotionEvent ev) { - final Callback cb = getCallback(); - return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) - : super.dispatchTouchEvent(ev); + final Callback cb = mWindow.getCallback(); + return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 + ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); } @Override public boolean dispatchTrackballEvent(MotionEvent ev) { - final Callback cb = getCallback(); - return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) - : super.dispatchTrackballEvent(ev); + final Callback cb = mWindow.getCallback(); + return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 + ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev); } @Override public boolean dispatchGenericMotionEvent(MotionEvent ev) { - final Callback cb = getCallback(); - return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchGenericMotionEvent(ev) - : super.dispatchGenericMotionEvent(ev); + final Callback cb = mWindow.getCallback(); + return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 + ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev); } public boolean superDispatchKeyEvent(KeyEvent event) { @@ -2508,7 +2536,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); - if (mHasNonClientDecor && mNonClientDecorView.mVisible) { + if (mHasNonClientDecor && mWindow.mNonClientDecorView.mVisible) { // Don't dispatch ACTION_DOWN to the non client decor if the window is // resizable and the event was (starting) outside the window. // Window resizing events should be handled by WindowManager. @@ -2531,7 +2559,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { int x = (int)event.getX(); int y = (int)event.getY(); if (isOutOfBounds(x, y)) { - closePanel(mFeatureId); + mWindow.closePanel(mFeatureId); return true; } } @@ -2557,7 +2585,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (action == MotionEvent.ACTION_MOVE) { if (y > (mDownY+30)) { Log.i(TAG, "Closing!"); - closePanel(mFeatureId); + mWindow.closePanel(mFeatureId); mWatchingForMenu = false; return true; } @@ -2573,7 +2601,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (action == MotionEvent.ACTION_DOWN) { int y = (int)event.getY(); - if (y >= (getHeight()-5) && !hasChildren()) { + if (y >= (getHeight()-5) && !mWindow.hasChildren()) { Log.i(TAG, "Watchiing!"); mWatchingForMenu = true; } @@ -2588,7 +2616,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (action == MotionEvent.ACTION_MOVE) { if (y < (getHeight()-30)) { Log.i(TAG, "Opening!"); - openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent( + mWindow.openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent( KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)); mWatchingForMenu = false; return true; @@ -2622,8 +2650,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { - final Callback cb = getCallback(); - if (cb != null && !isDestroyed()) { + final Callback cb = mWindow.getCallback(); + if (cb != null && !mWindow.isDestroyed()) { if (cb.dispatchPopulateAccessibilityEvent(event)) { return true; } @@ -2660,7 +2688,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (SWEEP_OPEN_MENU) { if (mMenuBackground == null && mFeatureId < 0 - && getAttributes().height + && mWindow.getAttributes().height == WindowManager.LayoutParams.MATCH_PARENT) { mMenuBackground = getContext().getDrawable( R.drawable.menu_background); @@ -2685,7 +2713,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { boolean fixedWidth = false; if (widthMode == AT_MOST) { - final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor; + final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor + : mWindow.mFixedWidthMajor; if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { final int w; if (tvw.type == TypedValue.TYPE_DIMENSION) { @@ -2706,7 +2735,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } if (heightMode == AT_MOST) { - final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor; + final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor + : mWindow.mFixedHeightMinor; if (tvh != null && tvh.type != TypedValue.TYPE_NULL) { final int h; if (tvh.type == TypedValue.TYPE_DIMENSION) { @@ -2724,21 +2754,21 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } - getOutsets(mOutsets); - if (mOutsets.top > 0 || mOutsets.bottom > 0) { + getOutsets(mWindow.mOutsets); + if (mWindow.mOutsets.top > 0 || mWindow.mOutsets.bottom > 0) { int mode = MeasureSpec.getMode(heightMeasureSpec); if (mode != MeasureSpec.UNSPECIFIED) { int height = MeasureSpec.getSize(heightMeasureSpec); heightMeasureSpec = MeasureSpec.makeMeasureSpec( - height + mOutsets.top + mOutsets.bottom, mode); + height + mWindow.mOutsets.top + mWindow.mOutsets.bottom, mode); } } - if (mOutsets.left > 0 || mOutsets.right > 0) { + if (mWindow.mOutsets.left > 0 || mWindow.mOutsets.right > 0) { int mode = MeasureSpec.getMode(widthMeasureSpec); if (mode != MeasureSpec.UNSPECIFIED) { int width = MeasureSpec.getSize(widthMeasureSpec); widthMeasureSpec = MeasureSpec.makeMeasureSpec( - width + mOutsets.left + mOutsets.right, mode); + width + mWindow.mOutsets.left + mWindow.mOutsets.right, mode); } } @@ -2750,7 +2780,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY); if (!fixedWidth && widthMode == AT_MOST) { - final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor; + final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor; if (tv.type != TypedValue.TYPE_NULL) { final int min; if (tv.type == TypedValue.TYPE_DIMENSION) { @@ -2778,12 +2808,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - getOutsets(mOutsets); - if (mOutsets.left > 0) { - offsetLeftAndRight(-mOutsets.left); + getOutsets(mWindow.mOutsets); + if (mWindow.mOutsets.left > 0) { + offsetLeftAndRight(-mWindow.mOutsets.left); } - if (mOutsets.top > 0) { - offsetTopAndBottom(-mOutsets.top); + if (mWindow.mOutsets.top > 0) { + offsetTopAndBottom(-mWindow.mOutsets.top); } } @@ -2799,23 +2829,23 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public boolean showContextMenuForChild(View originalView) { // Reuse the context menu builder - if (mContextMenu == null) { - mContextMenu = new ContextMenuBuilder(getContext()); - mContextMenu.setCallback(mContextMenuCallback); + if (mWindow.mContextMenu == null) { + mWindow.mContextMenu = new ContextMenuBuilder(getContext()); + mWindow.mContextMenu.setCallback(mWindow.mContextMenuCallback); } else { - mContextMenu.clearAll(); + mWindow.mContextMenu.clearAll(); } - final MenuDialogHelper helper = mContextMenu.show(originalView, + final MenuDialogHelper helper = mWindow.mContextMenu.show(originalView, originalView.getWindowToken()); if (helper != null) { - helper.setPresenterCallback(mContextMenuCallback); - } else if (mContextMenuHelper != null) { + helper.setPresenterCallback(mWindow.mContextMenuCallback); + } else if (mWindow.mContextMenuHelper != null) { // No menu to show, but if we have a menu currently showing it just became blank. // Close it. - mContextMenuHelper.dismiss(); + mWindow.mContextMenuHelper.dismiss(); } - mContextMenuHelper = helper; + mWindow.mContextMenuHelper = helper; return helper != null; } @@ -2845,14 +2875,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { View originatingView, ActionMode.Callback callback, int type) { ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); ActionMode mode = null; - if (getCallback() != null && !isDestroyed()) { + if (mWindow.getCallback() != null && !mWindow.isDestroyed()) { try { - mode = getCallback().onWindowStartingActionMode(wrappedCallback, type); + mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type); } catch (AbstractMethodError ame) { // Older apps might not implement the typed version of this method. if (type == ActionMode.TYPE_PRIMARY) { try { - mode = getCallback().onWindowStartingActionMode(wrappedCallback); + mode = mWindow.getCallback().onWindowStartingActionMode( + wrappedCallback); } catch (AbstractMethodError ame2) { // Older apps might not implement this callback method at all. } @@ -2877,9 +2908,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mode = null; } } - if (mode != null && getCallback() != null && !isDestroyed()) { + if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) { try { - getCallback().onActionModeStarted(mode); + mWindow.getCallback().onActionModeStarted(mode); } catch (AbstractMethodError ame) { // Older apps might not implement this callback method. } @@ -2968,10 +2999,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } private WindowInsets updateColorViews(WindowInsets insets, boolean animate) { - WindowManager.LayoutParams attrs = getAttributes(); + WindowManager.LayoutParams attrs = mWindow.getAttributes(); int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility(); - if (!mIsFloating && ActivityManager.isHighEndGfx()) { + if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) { boolean disallowAnimate = !isLaidOut(); disallowAnimate |= ((mLastWindowFlags ^ attrs.flags) & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; @@ -3003,14 +3034,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { boolean navBarToRightEdge = mLastBottomInset == 0 && mLastRightInset > 0; int navBarSize = navBarToRightEdge ? mLastRightInset : mLastBottomInset; - updateColorViewInt(mNavigationColorViewState, sysUiVisibility, mNavigationBarColor, - navBarSize, navBarToRightEdge, 0 /* rightInset */, - animate && !disallowAnimate); + updateColorViewInt(mNavigationColorViewState, sysUiVisibility, + mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge, + 0 /* rightInset */, animate && !disallowAnimate); boolean statusBarNeedsRightInset = navBarToRightEdge && mNavigationColorViewState.present; int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0; - updateColorViewInt(mStatusColorViewState, sysUiVisibility, mStatusBarColor, + updateColorViewInt(mStatusColorViewState, sysUiVisibility, mWindow.mStatusBarColor, mLastTopInset, false /* matchVertical */, statusBarRightInset, animate && !disallowAnimate); } @@ -3027,13 +3058,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { int consumedRight = consumingNavBar ? mLastRightInset : 0; int consumedBottom = consumingNavBar ? mLastBottomInset : 0; - if (mContentRoot != null - && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) { - MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams(); + if (mWindow.mContentRoot != null + && mWindow.mContentRoot.getLayoutParams() instanceof MarginLayoutParams) { + MarginLayoutParams lp = (MarginLayoutParams) mWindow.mContentRoot.getLayoutParams(); if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) { lp.rightMargin = consumedRight; lp.bottomMargin = consumedBottom; - mContentRoot.setLayoutParams(lp); + mWindow.mContentRoot.setLayoutParams(lp); if (insets == null) { // The insets have changed, but we're not currently in the process @@ -3071,11 +3102,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color, int size, boolean verticalBar, int rightMargin, boolean animate) { state.present = size > 0 && (sysUiVis & state.systemUiHideFlag) == 0 - && (getAttributes().flags & state.hideWindowFlag) == 0 - && (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; + && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0 + && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; boolean show = state.present && (color & Color.BLACK) != 0 - && (getAttributes().flags & state.translucentFlag) == 0; + && (mWindow.getAttributes().flags & state.translucentFlag) == 0; boolean visibilityChanged = false; View view = state.view; @@ -3167,14 +3198,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mPrimaryActionModeView.getLayoutParams(); boolean mlpChanged = false; if (mPrimaryActionModeView.isShown()) { - if (mTempRect == null) { - mTempRect = new Rect(); + if (mWindow.mTempRect == null) { + mWindow.mTempRect = new Rect(); } - final Rect rect = mTempRect; + final Rect rect = mWindow.mTempRect; // If the parent doesn't consume the insets, manually // apply the default system window insets. - mContentParent.computeSystemWindowInsets(insets, rect); + mWindow.mContentParent.computeSystemWindowInsets(insets, rect); final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0; if (mlp.topMargin != newMargin) { mlpChanged = true; @@ -3205,7 +3236,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // mode is overlaid on the app content (e.g. it's // sitting in a FrameLayout, see // screen_simple_overlay_action_mode.xml). - final boolean nonOverlay = (getLocalFeatures() + final boolean nonOverlay = (mWindow.getLocalFeatures() & (1 << FEATURE_ACTION_MODE_OVERLAY)) == 0; insets = insets.consumeSystemWindowInsets( false, nonOverlay && showStatusGuard /* top */, false, false); @@ -3229,14 +3260,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private void updateNavigationGuard(WindowInsets insets) { // IMEs lay out below the nav bar, but the content view must not (for back compat) - if (getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { + if (mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { // prevent the content view from including the nav bar height - if (mContentParent != null) { - if (mContentParent.getLayoutParams() instanceof MarginLayoutParams) { + if (mWindow.mContentParent != null) { + if (mWindow.mContentParent.getLayoutParams() instanceof MarginLayoutParams) { MarginLayoutParams mlp = - (MarginLayoutParams) mContentParent.getLayoutParams(); + (MarginLayoutParams) mWindow.mContentParent.getLayoutParams(); mlp.bottomMargin = insets.getSystemWindowInsetBottom(); - mContentParent.setLayoutParams(mlp); + mWindow.mContentParent.setLayoutParams(mlp); } } // position the navigation guard view, creating it if necessary @@ -3318,7 +3349,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mDefaultOpacity = opacity; if (mFeatureId < 0) { - setDefaultWindowFormat(opacity); + mWindow.setDefaultWindowFormat(opacity); } } @@ -3328,12 +3359,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // If the user is chording a menu shortcut, release the chord since // this window lost focus - if (hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus && mPanelChordingKey != 0) { - closePanel(FEATURE_OPTIONS_PANEL); + if (mWindow.hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus + && mWindow.mPanelChordingKey != 0) { + mWindow.closePanel(FEATURE_OPTIONS_PANEL); } - final Callback cb = getCallback(); - if (cb != null && !isDestroyed() && mFeatureId < 0) { + final Callback cb = mWindow.getCallback(); + if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) { cb.onWindowFocusChanged(hasWindowFocus); } @@ -3349,8 +3381,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { protected void onAttachedToWindow() { super.onAttachedToWindow(); - final Callback cb = getCallback(); - if (cb != null && !isDestroyed() && mFeatureId < 0) { + final Callback cb = mWindow.getCallback(); + if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) { cb.onAttachedToWindow(); } @@ -3362,7 +3394,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { * menu was open. When the activity is recreated, the menu * should be shown again. */ - openPanelsAfterRestore(); + mWindow.openPanelsAfterRestore(); } } @@ -3370,13 +3402,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - final Callback cb = getCallback(); + final Callback cb = mWindow.getCallback(); if (cb != null && mFeatureId < 0) { cb.onDetachedFromWindow(); } - if (mDecorContentParent != null) { - mDecorContentParent.dismissPopups(); + if (mWindow.mDecorContentParent != null) { + mWindow.mDecorContentParent.dismissPopups(); } if (mPrimaryActionModePopup != null) { @@ -3391,7 +3423,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mFloatingToolbar = null; } - PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); + PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false); if (st != null && st.menu != null && mFeatureId < 0) { st.menu.close(); } @@ -3400,29 +3432,29 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { @Override public void onCloseSystemDialogs(String reason) { if (mFeatureId >= 0) { - closeAllPanels(); + mWindow.closeAllPanels(); } } public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() { - return mFeatureId < 0 ? mTakeSurfaceCallback : null; + return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null; } public InputQueue.Callback willYouTakeTheInputQueue() { - return mFeatureId < 0 ? mTakeInputQueueCallback : null; + return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null; } public void setSurfaceType(int type) { - PhoneWindow.this.setType(type); + mWindow.setType(type); } public void setSurfaceFormat(int format) { - PhoneWindow.this.setFormat(format); + mWindow.setFormat(format); } public void setSurfaceKeepScreenOn(boolean keepOn) { - if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } @Override @@ -3454,7 +3486,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { endOnGoingFadeAnimation(); cleanupPrimaryActionMode(); if (mPrimaryActionModeView == null) { - if (isFloating()) { + if (mWindow.isFloating()) { // Use the action bar theme. final TypedValue outValue = new TypedValue(); final Theme baseTheme = mContext.getTheme(); @@ -3601,7 +3633,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private void setHandledFloatingActionMode(ActionMode mode) { mFloatingActionMode = mode; - mFloatingToolbar = new FloatingToolbar(mContext, PhoneWindow.this); + mFloatingToolbar = new FloatingToolbar(mContext, mWindow); ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar); mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. mFloatingActionModeOriginatingView.getViewTreeObserver() @@ -3640,6 +3672,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return windowHasNonClientDecor() && getElevation() > 0; } + void setWindow(PhoneWindow phoneWindow) { + mWindow = phoneWindow; + } + /** * Clears out internal references when the action mode is destroyed. */ @@ -3728,9 +3764,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { cleanupFloatingActionModeViews(); mFloatingActionMode = null; } - if (getCallback() != null && !isDestroyed()) { + if (mWindow.getCallback() != null && !mWindow.isDestroyed()) { try { - getCallback().onActionModeFinished(mode); + mWindow.getCallback().onActionModeFinished(mode); } catch (AbstractMethodError ame) { // Older apps might not implement this callback method. } @@ -3750,7 +3786,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } protected DecorView generateDecor(int featureId) { - return new DecorView(getContext(), featureId); + // System process doesn't have application context and in that case we need to directly use + // the context we have. Otherwise we want the application context, so we don't cling to the + // activity. + Context context = getContext().getApplicationContext(); + if (context == null) { + context = getContext(); + } + return new DecorView(context, featureId); } protected void setFeatureFromAttrs(int featureId, TypedArray attrs, @@ -3960,7 +4003,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { + Integer.toHexString(mFrameResource)); } } - mElevation = a.getDimension(R.styleable.Window_windowElevation, 0); + if (mLoadEleveation) { + mElevation = a.getDimension(R.styleable.Window_windowElevation, 0); + } mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false); mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT); } @@ -4032,8 +4077,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mNonClientDecorView = createNonClientDecorView(); View in = mLayoutInflater.inflate(layoutResource, null); if (mNonClientDecorView != null) { - decor.addView(mNonClientDecorView, - new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + if (mNonClientDecorView.getParent() == null) { + decor.addView(mNonClientDecorView, + new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + } mNonClientDecorView.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } else { decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); @@ -4096,6 +4143,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // Free floating overlapping windows require a non client decor with a caption and shadow.. private NonClientDecorView createNonClientDecorView() { NonClientDecorView nonClientDecorView = null; + for (int i = mDecor.getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) { + View view = mDecor.getChildAt(i); + if (view instanceof NonClientDecorView) { + // The decor was most likely saved from a relaunch - so reuse it. + nonClientDecorView = (NonClientDecorView) view; + mDecor.removeViewAt(i); + } + } final WindowManager.LayoutParams attrs = getAttributes(); boolean isApplication = attrs.type == TYPE_BASE_APPLICATION || attrs.type == TYPE_APPLICATION; @@ -4106,21 +4161,28 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mWorkspaceId < FIRST_DYNAMIC_STACK_ID) { // Dependent on the brightness of the used title we either use the // dark or the light button frame. - TypedValue value = new TypedValue(); - getContext().getTheme().resolveAttribute(R.attr.colorPrimary, value, true); - if (Color.luminance(value.data) < 0.5) { - nonClientDecorView = (NonClientDecorView) mLayoutInflater.inflate( - R.layout.non_client_decor_dark, null); - } else { - nonClientDecorView = (NonClientDecorView) mLayoutInflater.inflate( - R.layout.non_client_decor_light, null); + if (nonClientDecorView == null) { + TypedValue value = new TypedValue(); + getContext().getTheme().resolveAttribute(R.attr.colorPrimary, value, true); + // We can't use the application context inside the general inflater, because some + // views might depend on the fact that they get Activity or even specific activity. + // We control the NonClientDecor, so we know that application context should be + // safe enough. + LayoutInflater inflater = + mLayoutInflater.cloneInContext(getContext().getApplicationContext()); + if (Color.luminance(value.data) < 0.5) { + nonClientDecorView = (NonClientDecorView) inflater.inflate( + R.layout.non_client_decor_dark, null); + } else { + nonClientDecorView = (NonClientDecorView) inflater.inflate( + R.layout.non_client_decor_light, null); + } } nonClientDecorView.setPhoneWindow(this, hasNonClientDecor(mWorkspaceId), nonClientDecorHasShadow(mWorkspaceId)); } // Tell the decor if it has a visible non client decor. - mDecor.enableNonClientDecor(nonClientDecorView != null && - hasNonClientDecor(mWorkspaceId)); + mDecor.enableNonClientDecor(nonClientDecorView != null && hasNonClientDecor(mWorkspaceId)); return nonClientDecorView; } @@ -4131,14 +4193,17 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } private void installDecor() { + mForceDecorInstall = false; if (mDecor == null) { mDecor = generateDecor(-1); + mDecor.setWindow(this); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } + mDecor.setWindow(this); if (mContentParent == null) { mContentParent = generateLayout(mDecor); @@ -5307,4 +5372,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // TODO(skuhne): Add side by side mode here to add a decor. return workspaceId == FREEFORM_WORKSPACE_STACK_ID; } + + @Override + public boolean hasNonClientDecorView() { + return mNonClientDecorView != null; + } } diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java index 4c829a240e70..415f32528914 100644 --- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java +++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java @@ -10,16 +10,22 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; +import android.os.Handler; import android.os.Parcelable; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewTreeObserver; +import android.view.View.OnAttachStateChangeListener; import android.view.View.OnKeyListener; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.AdapterView; import android.widget.DropDownListView; +import android.widget.MenuItemHoverListener; import android.widget.ListView; import android.widget.MenuPopupWindow; +import android.widget.MenuPopupWindow.MenuDropDownListView; import android.widget.PopupWindow; import android.widget.PopupWindow.OnDismissListener; @@ -39,21 +45,144 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl private static final int HORIZ_POSITION_LEFT = 0; private static final int HORIZ_POSITION_RIGHT = 1; + private static final int SUBMENU_TIMEOUT_MS = 200; + private final Context mContext; private final int mMenuMaxWidth; private final int mPopupStyleAttr; private final int mPopupStyleRes; private final boolean mOverflowOnly; private final int mLayoutDirection; + private final Handler mSubMenuHoverHandler; + + private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (isShowing()) { + final View anchor = mShownAnchorView; + if (anchor == null || !anchor.isShown()) { + dismiss(); + } else if (isShowing()) { + // Recompute window sizes and positions. + for (MenuPopupWindow popup : mPopupWindows) { + popup.show(); + } + } + } + } + }; + + private final OnAttachStateChangeListener mAttachStateChangeListener = + new OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + if (mTreeObserver != null) { + if (!mTreeObserver.isAlive()) { + mTreeObserver = v.getViewTreeObserver(); + } + mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); + } + v.removeOnAttachStateChangeListener(this); + } + }; + + private final MenuItemHoverListener mMenuItemHoverListener = new MenuItemHoverListener() { + @Override + public void onItemHovered(MenuBuilder menu, int position) { + int menuIndex = -1; + for (int i = 0; i < mListViews.size(); i++) { + final MenuDropDownListView view = (MenuDropDownListView) mListViews.get(i); + final MenuAdapter adapter = (MenuAdapter) view.getAdapter(); + + if (adapter.getAdapterMenu() == menu) { + menuIndex = i; + break; + } + } + + if (menuIndex == -1) { + return; + } + + final MenuDropDownListView view = (MenuDropDownListView) mListViews.get(menuIndex); + final ListMenuItemView selectedItemView = (ListMenuItemView) view.getSelectedView(); + + if (selectedItemView != null && selectedItemView.isEnabled() + && selectedItemView.getItemData().hasSubMenu()) { + // If the currently selected item corresponds to a submenu, schedule to open the + // submenu on a timeout. + + mSubMenuHoverHandler.removeCallbacksAndMessages(null); + mSubMenuHoverHandler.postDelayed(new Runnable() { + @Override + public void run() { + // Make sure the submenu item is still the one selected. + if (view.getSelectedView() == selectedItemView + && selectedItemView.isEnabled() + && selectedItemView.getItemData().hasSubMenu()) { + // Close any other submenus that might be open at the current or + // a deeper level. + int nextIndex = mListViews.indexOf(view) + 1; + if (nextIndex < mListViews.size()) { + MenuAdapter nextSubMenuAdapter = + (MenuAdapter) mListViews.get(nextIndex).getAdapter(); + // Disable exit animation, to prevent overlapping fading out + // submenus. + mPopupWindows.get(nextIndex).setExitTransition(null); + nextSubMenuAdapter.getAdapterMenu().close(); + } + + // Then open the selected submenu. + view.performItemClick( + selectedItemView, + view.getSelectedItemPosition(), + view.getSelectedItemId()); + } + } + }, SUBMENU_TIMEOUT_MS); + } else if (menuIndex + 1 < mListViews.size()) { + // If the currently selected item does NOT corresponds to a submenu, check if there + // is a submenu already open that is one level deeper. If so, schedule to close it + // on a timeout. + + final MenuDropDownListView nextView = + (MenuDropDownListView) mListViews.get(menuIndex + 1); + final MenuAdapter nextAdapter = (MenuAdapter) nextView.getAdapter(); + + view.clearSelection(); + + mSubMenuHoverHandler.removeCallbacksAndMessages(null); + mSubMenuHoverHandler.postDelayed(new Runnable() { + @Override + public void run() { + // Make sure the menu wasn't already closed by something else and that + // it wasn't re-hovered by the user since this was scheduled. + int nextMenuIndex = mListViews.indexOf(nextView); + if (nextMenuIndex != -1 && nextView.getSelectedView() == null) { + // Disable exit animation, to prevent overlapping fading out submenus. + mPopupWindows.get(nextMenuIndex).setExitTransition(null); + nextAdapter.getAdapterMenu().close(); + } + } + }, SUBMENU_TIMEOUT_MS); + } + } + }; private int mDropDownGravity = Gravity.NO_GRAVITY; - private View mAnchor; + private View mAnchorView; + private View mShownAnchorView; private List<DropDownListView> mListViews; private List<MenuPopupWindow> mPopupWindows; private List<int[]> mOffsets; private int mPreferredPosition; private boolean mForceShowIcon; private Callback mPresenterCallback; + private ViewTreeObserver mTreeObserver; private PopupWindow.OnDismissListener mOnDismissListener; /** @@ -64,7 +193,7 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl public CascadingMenuPopup(Context context, View anchor, int popupStyleAttr, int popupStyleRes, boolean overflowOnly) { mContext = Preconditions.checkNotNull(context); - mAnchor = Preconditions.checkNotNull(anchor); + mAnchorView = Preconditions.checkNotNull(anchor); mPopupStyleAttr = popupStyleAttr; mPopupStyleRes = popupStyleRes; mOverflowOnly = overflowOnly; @@ -82,6 +211,7 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl mPopupWindows = new ArrayList<MenuPopupWindow>(); mListViews = new ArrayList<DropDownListView>(); mOffsets = new ArrayList<int[]>(); + mSubMenuHoverHandler = new Handler(); } @Override @@ -92,12 +222,12 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl private MenuPopupWindow createPopupWindow() { MenuPopupWindow popupWindow = new MenuPopupWindow( mContext, null, mPopupStyleAttr, mPopupStyleRes); + popupWindow.setHoverListener(mMenuItemHoverListener); popupWindow.setOnItemClickListener(this); popupWindow.setOnDismissListener(this); - popupWindow.setAnchorView(mAnchor); + popupWindow.setAnchorView(mAnchorView); popupWindow.setDropDownGravity(mDropDownGravity); popupWindow.setModal(true); - popupWindow.setTouchModal(false); return popupWindow; } @@ -116,6 +246,16 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl popupWindow.show(); mListViews.add((DropDownListView) popupWindow.getListView()); } + + mShownAnchorView = mAnchorView; + if (mShownAnchorView != null) { + final boolean addGlobalListener = mTreeObserver == null; + mTreeObserver = mShownAnchorView.getViewTreeObserver(); // Refresh to latest + if (addGlobalListener) { + mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener); + } + mShownAnchorView.addOnAttachStateChangeListener(mAttachStateChangeListener); + } } @Override @@ -160,7 +300,7 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl lastListView.getLocationOnScreen(screenLocation); final Rect displayFrame = new Rect(); - mAnchor.getWindowVisibleDisplayFrame(displayFrame); + mShownAnchorView.getWindowVisibleDisplayFrame(displayFrame); if (mPreferredPosition == HORIZ_POSITION_RIGHT) { final int right = screenLocation[0] + lastListView.getWidth() + nextMenuWidth; @@ -196,6 +336,7 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl int y = 0; if (addSubMenu) { + popupWindow.setTouchModal(false); popupWindow.setEnterTransition(null); ListView lastListView = mListViews.get(mListViews.size() - 1); @@ -319,7 +460,7 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl if (menuIndex == -1 && menu == adapter.mAdapterMenu) { menuIndex = i; - wasSelected = view.getSelectedItem() != null; + wasSelected = view.getSelectedView() != null; } // Once the menu has been found, remove it and all submenus beneath it from the @@ -352,6 +493,13 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl } if (mPopupWindows.size() == 0) { + if (mTreeObserver != null) { + if (mTreeObserver.isAlive()) { + mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); + } + mTreeObserver = null; + } + mShownAnchorView.removeOnAttachStateChangeListener(mAttachStateChangeListener); // If every [sub]menu was dismissed, that means the whole thing was dismissed, so notify // the owner. mOnDismissListener.onDismiss(); @@ -379,7 +527,7 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl @Override public void setAnchorView(View anchor) { - mAnchor = anchor; + mAnchorView = anchor; } @Override @@ -391,4 +539,5 @@ final class CascadingMenuPopup extends MenuPopup implements AdapterView.OnItemCl public ListView getListView() { return mListViews.size() > 0 ? mListViews.get(mListViews.size() - 1) : null; } + }
\ No newline at end of file diff --git a/core/java/com/android/internal/view/menu/MenuAdapter.java b/core/java/com/android/internal/view/menu/MenuAdapter.java index 1e03b1f8b971..673cfd12d878 100644 --- a/core/java/com/android/internal/view/menu/MenuAdapter.java +++ b/core/java/com/android/internal/view/menu/MenuAdapter.java @@ -40,6 +40,10 @@ public class MenuAdapter extends BaseAdapter { findExpandedIndex(); } + public boolean getForceShowIcon() { + return mForceShowIcon; + } + public void setForceShowIcon(boolean forceShow) { mForceShowIcon = forceShow; } diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index e0d7feefb1b0..ea7998339f3b 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -16,10 +16,11 @@ package com.android.internal.view.menu; +import com.android.internal.view.menu.MenuPresenter.Callback; + import android.content.Context; import android.view.Gravity; import android.view.View; -import android.view.ViewTreeObserver; import android.widget.PopupWindow; /** @@ -27,8 +28,7 @@ import android.widget.PopupWindow; * * @hide */ -public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, - PopupWindow.OnDismissListener, View.OnAttachStateChangeListener { +public class MenuPopupHelper implements PopupWindow.OnDismissListener { private final Context mContext; private final MenuBuilder mMenu; private final boolean mOverflowOnly; @@ -37,9 +37,9 @@ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, private View mAnchorView; private MenuPopup mPopup; - private ViewTreeObserver mTreeObserver; private int mDropDownGravity = Gravity.NO_GRAVITY; + private Callback mPresenterCallback; public MenuPopupHelper(Context context, MenuBuilder menu) { this(context, menu, null, false, com.android.internal.R.attr.popupMenuStyle, 0); @@ -114,18 +114,15 @@ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, return true; } - final View anchor = mAnchorView; - if (anchor != null) { - final boolean addGlobalListener = mTreeObserver == null; - mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest - if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this); - anchor.addOnAttachStateChangeListener(this); - mPopup.setAnchorView(anchor); - mPopup.setGravity(mDropDownGravity); - } else { + if (mAnchorView == null) { return false; } + mPopup = createMenuPopup(); + mPopup.setAnchorView(mAnchorView); + mPopup.setGravity(mDropDownGravity); + mPopup.setCallback(mPresenterCallback); + // In order for subclasses of MenuPopupHelper to satisfy the OnDismissedListener interface, // we must set the listener to this outer Helper rather than to the inner MenuPopup. // Not to worry -- the inner MenuPopup will call our own #onDismiss method after it's done @@ -146,45 +143,15 @@ public class MenuPopupHelper implements ViewTreeObserver.OnGlobalLayoutListener, @Override public void onDismiss() { mPopup = null; - if (mTreeObserver != null) { - if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver(); - mTreeObserver.removeGlobalOnLayoutListener(this); - mTreeObserver = null; - } - mAnchorView.removeOnAttachStateChangeListener(this); } public boolean isShowing() { return mPopup != null && mPopup.isShowing(); } - @Override - public void onGlobalLayout() { - if (isShowing()) { - final View anchor = mAnchorView; - if (anchor == null || !anchor.isShown()) { - dismiss(); - } else if (isShowing()) { - // Recompute window size and position - mPopup.show(); - } - } - } - - @Override - public void onViewAttachedToWindow(View v) { - } - - @Override - public void onViewDetachedFromWindow(View v) { - if (mTreeObserver != null) { - if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver(); - mTreeObserver.removeGlobalOnLayoutListener(this); - } - v.removeOnAttachStateChangeListener(this); - } public void setCallback(MenuPresenter.Callback cb) { + mPresenterCallback = cb; mPopup.setCallback(cb); } } diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java index 9a30ffafb75d..8877f3d96a39 100644 --- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java +++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java @@ -9,7 +9,10 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.View.OnAttachStateChangeListener; import android.view.View.OnKeyListener; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.ViewTreeObserver; import android.widget.AdapterView; import android.widget.ListView; import android.widget.MenuPopupWindow; @@ -34,15 +37,53 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On private final int mPopupMaxWidth; private final int mPopupStyleAttr; private final int mPopupStyleRes; + // The popup window is final in order to couple its lifecycle to the lifecycle of the + // StandardMenuPopup. + private final MenuPopupWindow mPopup; + + private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (isShowing()) { + final View anchor = mShownAnchorView; + if (anchor == null || !anchor.isShown()) { + dismiss(); + } else if (isShowing()) { + // Recompute window size and position + mPopup.show(); + } + } + } + }; + + private final OnAttachStateChangeListener mAttachStateChangeListener = + new OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + if (mTreeObserver != null) { + if (!mTreeObserver.isAlive()) mTreeObserver = v.getViewTreeObserver(); + mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); + } + v.removeOnAttachStateChangeListener(this); + } + }; private PopupWindow.OnDismissListener mOnDismissListener; private View mAnchorView; - private MenuPopupWindow mPopup; + private View mShownAnchorView; private Callback mPresenterCallback; + private ViewTreeObserver mTreeObserver; private ViewGroup mMeasureParent; + /** Whether the popup has been dismissed. Once dismissed, it cannot be opened again. */ + private boolean mWasDismissed; + /** Whether the cached content width value is valid. */ private boolean mHasContentWidth; @@ -88,18 +129,26 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On return true; } + if (mWasDismissed || mAnchorView == null) { + return false; + } + + mShownAnchorView = mAnchorView; + mPopup.setOnDismissListener(this); mPopup.setOnItemClickListener(this); mPopup.setAdapter(mAdapter); mPopup.setModal(true); - final View anchor = mAnchorView; - if (anchor != null) { - mPopup.setAnchorView(anchor); - mPopup.setDropDownGravity(mDropDownGravity); - } else { - return false; + final View anchor = mShownAnchorView; + final boolean addGlobalListener = mTreeObserver == null; + mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest + if (addGlobalListener) { + mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener); } + anchor.addOnAttachStateChangeListener(mAttachStateChangeListener); + mPopup.setAnchorView(anchor); + mPopup.setDropDownGravity(mDropDownGravity); if (!mHasContentWidth) { mContentWidth = measureIndividualMenuWidth( @@ -141,14 +190,20 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On @Override public boolean isShowing() { - return mPopup != null && mPopup.isShowing(); + return !mWasDismissed && mPopup.isShowing(); } @Override public void onDismiss() { - mPopup = null; + mWasDismissed = true; mMenu.close(); + if (mTreeObserver != null) { + if (!mTreeObserver.isAlive()) mTreeObserver = mShownAnchorView.getViewTreeObserver(); + mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); + mTreeObserver = null; + } + mShownAnchorView.removeOnAttachStateChangeListener(mAttachStateChangeListener); mOnDismissListener.onDismiss(); } @@ -170,19 +225,10 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On public boolean onSubMenuSelected(SubMenuBuilder subMenu) { if (subMenu.hasVisibleItems()) { MenuPopupHelper subPopup = new MenuPopupHelper( - mContext, subMenu, mAnchorView, mOverflowOnly, mPopupStyleAttr, mPopupStyleRes); + mContext, subMenu, mShownAnchorView, mOverflowOnly, mPopupStyleAttr, + mPopupStyleRes); subPopup.setCallback(mPresenterCallback); - - boolean preserveIconSpacing = false; - final int count = subMenu.size(); - for (int i = 0; i < count; i++) { - MenuItem childItem = subMenu.getItem(i); - if (childItem.isVisible() && childItem.getIcon() != null) { - preserveIconSpacing = true; - break; - } - } - subPopup.setForceShowIcon(preserveIconSpacing); + subPopup.setForceShowIcon(mAdapter.getForceShowIcon()); if (subPopup.tryShow()) { if (mPresenterCallback != null) { @@ -210,7 +256,6 @@ final class StandardMenuPopup extends MenuPopup implements OnDismissListener, On return false; } - @Override public Parcelable onSaveInstanceState() { return null; diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 6a98ec70f92e..f7e9add58f21 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1075,12 +1075,22 @@ public class LockPatternUtils { * enter a pattern. */ public long getLockoutAttemptDeadline(int userId) { - final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L, userId); + long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L, userId); final long timeoutMs = getLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, 0L, userId); final long now = SystemClock.elapsedRealtime(); - if (deadline < now || deadline > (now + timeoutMs)) { + if (deadline < now) { + // timeout expired + setLong(LOCKOUT_ATTEMPT_DEADLINE, 0, userId); + setLong(LOCKOUT_ATTEMPT_TIMEOUT_MS, 0, userId); return 0L; } + + if (deadline > (now + timeoutMs)) { + // device was rebooted, set new deadline + deadline = now + timeoutMs; + setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline, userId); + } + return deadline; } diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp index b9e48a0c0cc8..80de52611735 100644 --- a/core/jni/android/graphics/SurfaceTexture.cpp +++ b/core/jni/android/graphics/SurfaceTexture.cpp @@ -241,17 +241,16 @@ static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached, BufferQueue::createBufferQueue(&producer, &consumer); if (singleBufferMode) { - consumer->disableAsyncBuffer(); - consumer->setDefaultMaxBufferCount(1); + consumer->setMaxBufferCount(1); } sp<GLConsumer> surfaceTexture; if (isDetached) { surfaceTexture = new GLConsumer(consumer, GL_TEXTURE_EXTERNAL_OES, - true, true); + true, !singleBufferMode); } else { surfaceTexture = new GLConsumer(consumer, texName, - GL_TEXTURE_EXTERNAL_OES, true, true); + GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode); } if (surfaceTexture == 0) { diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml index 2473e875bf65..bb347cb249b8 100644 --- a/core/res/res/layout-land/time_picker_material.xml +++ b/core/res/res/layout-land/time_picker_material.xml @@ -46,7 +46,8 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:layout_centerInParent="true" - android:paddingTop="@dimen/timepicker_radial_picker_top_margin"> + android:paddingTop="@dimen/timepicker_radial_picker_top_margin" + android:layout_marginBottom="-12dp"> <!-- The hour should always be to the left of the separator, regardless of the current locale's layout direction. --> @@ -57,14 +58,16 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="right" /> + android:gravity="right" + android:includeFontPadding="false" /> <TextView android:id="@+id/separator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" - android:importantForAccessibility="no" /> + android:importantForAccessibility="no" + android:includeFontPadding="false" /> <!-- The minutes should always be to the right of the separator, regardless of the current locale's layout direction. --> @@ -75,7 +78,8 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="left" /> + android:gravity="left" + android:includeFontPadding="false" /> </LinearLayout> <!-- The layout alignment of this view will switch between toRightOf @@ -93,22 +97,27 @@ android:id="@+id/am_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minHeight="48dp" + android:minWidth="48dp" + android:gravity="bottom" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:paddingStart="@dimen/timepicker_ampm_horizontal_padding" android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding" - android:paddingTop="@dimen/timepicker_am_top_padding" + android:paddingTop="4dp" + android:paddingBottom="6dp" android:lines="1" - android:ellipsize="none" - android:includeFontPadding="false" /> + android:ellipsize="none" /> <CheckedTextView android:id="@+id/pm_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minHeight="48dp" + android:minWidth="48dp" + android:gravity="top" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:paddingStart="@dimen/timepicker_ampm_horizontal_padding" android:paddingEnd="@dimen/timepicker_ampm_horizontal_padding" - android:paddingTop="@dimen/timepicker_pm_top_padding" android:lines="1" android:ellipsize="none" android:includeFontPadding="false" /> diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml index 3c3a8a8ff3ed..acdc5090f41e 100644 --- a/core/res/res/layout/time_picker_header_material.xml +++ b/core/res/res/layout/time_picker_header_material.xml @@ -78,6 +78,9 @@ android:id="@+id/am_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="48dp" + android:minHeight="48dp" + android:gravity="bottom" android:paddingTop="@dimen/timepicker_am_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" @@ -86,6 +89,9 @@ android:id="@+id/pm_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="48dp" + android:minHeight="48dp" + android:gravity="top" android:paddingTop="@dimen/timepicker_pm_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" diff --git a/core/tests/BTtraffic/Android.mk b/core/tests/BTtraffic/Android.mk new file mode 100644 index 000000000000..7d8352717a34 --- /dev/null +++ b/core/tests/BTtraffic/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR := \ + $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := bttraffic +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/core/tests/BTtraffic/AndroidManifest.xml b/core/tests/BTtraffic/AndroidManifest.xml new file mode 100644 index 000000000000..00d9707de2bf --- /dev/null +++ b/core/tests/BTtraffic/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.experimental.bttraffic" > + + <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> + + <uses-sdk + android:minSdkVersion="18" + android:targetSdkVersion="18" + /> + <application + android:allowBackup="false" + android:label="@string/app_name" > + <service + android:name=".BTtraffic" + android:enabled="true" + android:exported="true" > + </service> + </application> + +</manifest> diff --git a/core/tests/BTtraffic/README b/core/tests/BTtraffic/README new file mode 100644 index 000000000000..430488f656f9 --- /dev/null +++ b/core/tests/BTtraffic/README @@ -0,0 +1,45 @@ +This is a tool to generate classic Bluetooth traffic with specified period and package size. +Together with the SvcMonitor, which will be called automatically in this android service, can be +used to measure the CPU usage from the Java layer Bluetooth code and the underlying system service +com.android.bluetooth. + +1. Server (Listener) - Client (Sender) model. Both run as an Android service. +2. No pairing needed. Communicate via unsecured RFcomm. Client establishes the connection by +providing the MAC addr of the server. +3. Bluetooth has to be turned on on both side. +4. Client can configure the traffic by specifying the transfer period and package size. +5. A separate monitor process will be automatically forked and will be reading from /proc file +system to calculate the cpu usage. The measurement is updated once per second. +6. The monitor process (com.google.android.experimental.svcmonitor/.ScvMonitor) can be run as an +independent service to measure cpu usage on any similarly configured tests (e.g. wifi, BLE). Refer +to SvcMonitor's README for usage and details. + +Usage: +To instal the test: +On both the server and client device, install the 2 apk: +$ adb install $OUT/system/app/bttraffic/bttraffic.apk +$ adb install $OUT/system/app/svcmonitor/svcmonitor.apk + +To start the service on the SERVER side: +$ adb shell am startservice -a start --ez ack true \ +com.google.android.experimental.bttraffic/.BTtraffic + +To start the service on the CLIENT side: +$ adb shell am startservice -a start \ +-e addr "F8:A9:D0:A8:74:8E" --ei size 1000 --ei period 15 \ +com.google.android.experimental.bttraffic/.BTtraffic + +To stop the test: +On either the server or client: +$ adb shell am startservice -a stop \ +com.google.android.experimental.bttraffic/.BTtraffic + +To look at the data: +$ adb logcat | grep bttraffic + +Options: +-e addr: MAC addr of the server, in uppercase letter. +--ei size: package size, unit: byte; default: 1024, MAX: 20MB +--ei period: system sleep time between sending each package, unit: ms, default: 5000 + ** if -1 is provided, client will only send the package once. +--ez ack: whether acknowledge is required (true/false) diff --git a/core/tests/BTtraffic/res/values/strings.xml b/core/tests/BTtraffic/res/values/strings.xml new file mode 100644 index 000000000000..e70276e03647 --- /dev/null +++ b/core/tests/BTtraffic/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">Bluetooth Test</string> +</resources> diff --git a/core/tests/BTtraffic/src/com/android/google/experimental/bttraffic/BTtraffic.java b/core/tests/BTtraffic/src/com/android/google/experimental/bttraffic/BTtraffic.java new file mode 100644 index 000000000000..286c0aa2915f --- /dev/null +++ b/core/tests/BTtraffic/src/com/android/google/experimental/bttraffic/BTtraffic.java @@ -0,0 +1,328 @@ +package com.google.android.experimental.bttraffic; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.os.SystemClock; +import android.util.Log; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.Exception; +import java.lang.Runtime; +import java.lang.RuntimeException; +import java.lang.Process; +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.Set; +import java.util.UUID; + +public class BTtraffic extends Service { + public static final String TAG = "bttraffic"; + static final String SERVICE_NAME = "bttraffic"; + static final String SYS_SERVICE_NAME = "com.android.bluetooth"; + static final UUID SERVICE_UUID = UUID.fromString("5e8945b0-1234-5432-a5e2-0800200c9a67"); + volatile Thread mWorkerThread; + volatile boolean isShuttingDown = false; + volatile boolean isServer = false; + + public BTtraffic() {} + + static void safeClose(Closeable closeable) { + try { + closeable.close(); + } catch (IOException e) { + Log.d(TAG, "Unable to close resource.\n"); + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent == null) { + stopSelf(); + return 0; + } + if ("stop".equals(intent.getAction())) { + stopService(); + } else if ("start".equals(intent.getAction())) { + startWorker(intent); + } else { + Log.d(TAG, "unknown action: + " + intent.getAction()); + } + return 0; + } + + private void startWorker(Intent intent) { + if (mWorkerThread != null) { + Log.d(TAG, "worker thread already active"); + return; + } + isShuttingDown = false; + String remoteAddr = intent.getStringExtra("addr"); + Log.d(TAG, "startWorker: addr=" + remoteAddr); + Runnable worker = + remoteAddr == null + ? new ListenerRunnable(this, intent) + : new SenderRunnable(this, remoteAddr, intent); + isServer = remoteAddr == null ? true: false; + mWorkerThread = new Thread(worker, "BTtrafficWorker"); + try { + startMonitor(); + Log.d(TAG, "Monitor service started"); + mWorkerThread.start(); + Log.d(TAG, "Worker thread started"); + } catch (Exception e) { + Log.d(TAG, "Failed to start service", e); + } + } + + private void startMonitor() + throws Exception { + if (isServer) { + Log.d(TAG, "Start monitor on server"); + String[] startmonitorCmd = { + "/system/bin/am", + "startservice", + "-a", "start", + "-e", "java", SERVICE_NAME, + "-e", "hal", SYS_SERVICE_NAME, + "com.google.android.experimental.svcmonitor/.SvcMonitor" + }; + Process ps = new ProcessBuilder() + .command(startmonitorCmd) + .redirectErrorStream(true) + .start(); + } else { + Log.d(TAG, "No need to start SvcMonitor on client"); + } + } + + private void stopMonitor() + throws Exception { + if (isServer) { + Log.d(TAG, "StopMonitor on server"); + String[] stopmonitorCmd = { + "/system/bin/am", + "startservice", + "-a", "stop", + "com.google.android.experimental.svcmonitor/.SvcMonitor" + }; + Process ps = new ProcessBuilder() + .command(stopmonitorCmd) + .redirectErrorStream(true) + .start(); + } else { + Log.d(TAG, "No need to stop Svcmonitor on client"); + } + } + + public void stopService() { + if (mWorkerThread == null) { + Log.d(TAG, "no active thread"); + return; + } + + isShuttingDown = true; + + try { + stopMonitor(); + } catch (Exception e) { + Log.d(TAG, "Unable to stop SvcMonitor!", e); + } + + if (Thread.currentThread() != mWorkerThread) { + mWorkerThread.interrupt(); + Log.d(TAG, "Interrupting thread"); + try { + mWorkerThread.join(); + } catch (InterruptedException e) { + Log.d(TAG, "Unable to join thread!"); + } + } + + mWorkerThread = null; + stopSelf(); + Log.d(TAG, "Service stopped"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + public static class ListenerRunnable implements Runnable { + private final BTtraffic bttraffic; + private final boolean sendAck; + private Intent intent; + private final int maxbuffersize = 20 * 1024 * 1024; + + public ListenerRunnable(BTtraffic bttraffic, Intent intent) { + this.bttraffic = bttraffic; + this.sendAck = intent.getBooleanExtra("ack", true); + this.intent = intent; + } + + @Override + public void run() { + BluetoothServerSocket serverSocket; + + try { + Log.d(TAG, "getting server socket"); + serverSocket = BluetoothAdapter.getDefaultAdapter() + .listenUsingInsecureRfcommWithServiceRecord( + SERVICE_NAME, SERVICE_UUID); + } catch (IOException e) { + Log.d(TAG, "error creating server socket, stopping thread"); + bttraffic.stopService(); + return; + } + + Log.d(TAG, "got server socket, starting accept loop"); + BluetoothSocket socket = null; + try { + Log.d(TAG, "accepting"); + socket = serverSocket.accept(); + + if (!Thread.interrupted()) { + Log.d(TAG, "accepted, listening"); + doListening(socket.getInputStream(), socket.getOutputStream()); + Log.d(TAG, "listen finished"); + } + } catch (IOException e) { + Log.d(TAG, "error while accepting or listening", e); + } finally { + Log.d(TAG, "Linster interruped"); + Log.d(TAG, "closing socket and stopping service"); + safeClose(serverSocket); + safeClose(socket); + if (!bttraffic.isShuttingDown) + bttraffic.stopService(); + } + + } + + private void doListening(InputStream inputStream, OutputStream outputStream) + throws IOException { + ByteBuffer byteBuffer = ByteBuffer.allocate(maxbuffersize); + + while (!Thread.interrupted()) { + readBytesIntoBuffer(inputStream, byteBuffer, 4); + byteBuffer.flip(); + int length = byteBuffer.getInt(); + if (Thread.interrupted()) + break; + readBytesIntoBuffer(inputStream, byteBuffer, length); + + if (sendAck) + outputStream.write(0x55); + } + } + + void readBytesIntoBuffer(InputStream inputStream, ByteBuffer byteBuffer, int numToRead) + throws IOException { + byteBuffer.clear(); + while (true) { + int position = byteBuffer.position(); + int remaining = numToRead - position; + if (remaining == 0) { + break; + } + int count = inputStream.read(byteBuffer.array(), position, remaining); + if (count < 0) { + throw new IOException("read the EOF"); + } + byteBuffer.position(position + count); + } + } + } + + public static class SenderRunnable implements Runnable { + private final BTtraffic bttraffic; + private final String remoteAddr; + private final int pkgsize, period; + private final int defaultpkgsize = 1024; + private final int defaultperiod = 5000; + private static ByteBuffer lengthBuffer = ByteBuffer.allocate(4); + + public SenderRunnable(BTtraffic bttraffic, String remoteAddr, Intent intent) { + this.bttraffic = bttraffic; + this.remoteAddr = remoteAddr; + this.pkgsize = intent.getIntExtra("size", defaultpkgsize); + this.period = intent.getIntExtra("period", defaultperiod); + } + + @Override + public void run() { + BluetoothDevice device = null; + try { + device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddr); + } catch (IllegalArgumentException e) { + Log.d(TAG, "Invalid BT MAC address!\n"); + } + if (device == null) { + Log.d(TAG, "can't find matching device, stopping thread and service"); + bttraffic.stopService(); + return; + } + + BluetoothSocket socket = null; + try { + Log.d(TAG, "connecting to device with MAC addr: " + remoteAddr); + socket = device.createInsecureRfcommSocketToServiceRecord(SERVICE_UUID); + socket.connect(); + Log.d(TAG, "connected, starting to send"); + doSending(socket.getOutputStream()); + Log.d(TAG, "send stopped, stopping service"); + } catch (Exception e) { + Log.d(TAG, "error while sending", e); + } finally { + Log.d(TAG, "finishing, closing thread and service"); + safeClose(socket); + if (!bttraffic.isShuttingDown) + bttraffic.stopService(); + } + } + + private void doSending(OutputStream outputStream) throws IOException { + Log.w(TAG, "doSending"); + try { + Random random = new Random(System.currentTimeMillis()); + + byte[] bytes = new byte[pkgsize]; + random.nextBytes(bytes); + while (!Thread.interrupted()) { + writeBytes(outputStream, bytes.length); + outputStream.write(bytes, 0, bytes.length); + if (period < 0) + break; + if (period == 0) + continue; + + SystemClock.sleep(period); + } + Log.d(TAG, "Sender interrupted"); + } catch (IOException e) { + Log.d(TAG, "doSending got error", e); + } + } + + private static void writeBytes(OutputStream outputStream, int value) throws IOException { + lengthBuffer.putInt(value); + lengthBuffer.flip(); + outputStream.write(lengthBuffer.array(), lengthBuffer.position(), lengthBuffer.limit()); + } + } + +} diff --git a/core/tests/SvcMonitor/Android.mk b/core/tests/SvcMonitor/Android.mk new file mode 100644 index 000000000000..2b8045506f02 --- /dev/null +++ b/core/tests/SvcMonitor/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR := \ + $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := svcmonitor +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/core/tests/SvcMonitor/AndroidManifest.xml b/core/tests/SvcMonitor/AndroidManifest.xml new file mode 100644 index 000000000000..de5a9bdaed41 --- /dev/null +++ b/core/tests/SvcMonitor/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.experimental.svcmonitor" > + + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> + + <uses-sdk + android:minSdkVersion="18" + android:targetSdkVersion="18" + /> + <application + android:allowBackup="false" + android:label="@string/app_name" > + <service + android:name=".SvcMonitor" + android:enabled="true" + android:exported="true" > + </service> + </application> + +</manifest> diff --git a/core/tests/SvcMonitor/README b/core/tests/SvcMonitor/README new file mode 100644 index 000000000000..13a4380589b4 --- /dev/null +++ b/core/tests/SvcMonitor/README @@ -0,0 +1,27 @@ +This Android service measures CPU usage of a program and an underlying system service it relies on. +An example of this would be an android app XYZ communicates to some other device via Bluetooth. The +SvcMonitor service can monitor the CPU usage of XYZ and com.android.bluetooth. + +Usage: + +To start the service: +$ adb shell am startservice -a start \ +-e java XYZ -e hal com.android.bluetooth \ +com.google.android.experimental.svcmonitor/.SvcMonitor + +To stop the service: +$ adb shell am startservice -a stop \ +com.google.android.experimental.svcmonitor/.SvcMonitor + +To stop the service config: +$ adb shell am startservice -a change \ +-e java NewName -e hal NewService \ +com.google.android.experimental.svcmonitor/.SvcMonitor + +To monitor the data: +$ adb logcat | grep XYZ + +Options: +-e java NameOfProgram: any running process’s name. +-e hal NameOfSysService: name of the system service the previous process relies on. +--ei period: period between each measurement (frequency). Unit: ms, Default:1000, Min: 100 diff --git a/core/tests/SvcMonitor/res/values/strings.xml b/core/tests/SvcMonitor/res/values/strings.xml new file mode 100644 index 000000000000..e70276e03647 --- /dev/null +++ b/core/tests/SvcMonitor/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="app_name">Bluetooth Test</string> +</resources> diff --git a/core/tests/SvcMonitor/src/com/android/google/experimental/svcmoniter/SvcMonitor.java b/core/tests/SvcMonitor/src/com/android/google/experimental/svcmoniter/SvcMonitor.java new file mode 100644 index 000000000000..a451445530cd --- /dev/null +++ b/core/tests/SvcMonitor/src/com/android/google/experimental/svcmoniter/SvcMonitor.java @@ -0,0 +1,209 @@ +package com.google.android.experimental.svcmonitor; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.SystemClock; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.lang.Runnable; +import java.lang.Thread; +import java.util.Set; + +public class SvcMonitor extends Service { + public static final String TAG = "svcmonitor"; + String javaProc, halProc; + volatile Thread tMonitor; + int period; + + public SvcMonitor() {}; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent == null) { + stopSelf(); + return 0; + } + Log.d(TAG, "Starting SvcMonitor"); + if ("stop".equals(intent.getAction())) { + stopService(); + } else if ("start".equals(intent.getAction())) { + startMonitor(intent); + } else if ("change".equals(intent.getAction())) { + changeConfig(intent); + } else { + Log.d(TAG, "unknown action: + " + intent.getAction()); + } + return 0; + } + + private void changeConfig(Intent intent) { + if (tMonitor == null) { + Log.d(TAG, "Service not active. Start service first"); + return; + } + stopThread(); + startMonitor(intent); + } + + private void startMonitor(Intent intent) { + if (tMonitor != null) { + Log.d(TAG, "thread already active"); + return; + } + javaProc = intent.getStringExtra("java"); + halProc = intent.getStringExtra("hal"); + period = intent.getIntExtra("period", 1000); + if (javaProc == null || halProc == null || period < 100) { + Log.d(TAG, "Failed starting monitor, invalid arguments."); + stopSelf(); + return; + } + Runnable monitor = new MonitorRunnable(this); + tMonitor = new Thread(monitor); + tMonitor.start(); + } + + private void stopService() { + stopThread(); + stopSelf(); + Log.d(TAG, "SvcMonitor stopped"); + } + + private void stopThread() { + if (tMonitor == null) { + Log.d(TAG, "no active thread"); + return; + } + Log.d(TAG, "interrupting monitor thread"); + tMonitor.interrupt(); + try { + tMonitor.join(); + } catch (InterruptedException e) { + Log.d(TAG, "Unable to finish monitor thread"); + } + tMonitor = null; + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + public static class MonitorRunnable implements Runnable { + long java_time_old, hal_time_old, cpu_time_old = -1; + String javaPID, halPID; + SvcMonitor svcmonitor; + static String javaProcTAG; + int period; + + public MonitorRunnable(SvcMonitor svcmonitor) { + this.svcmonitor = svcmonitor; + this.period = svcmonitor.period; + javaPID = getPIDof(svcmonitor.javaProc); + halPID = getPIDof(svcmonitor.halProc); + java_time_old = getPsTime(javaPID); + hal_time_old = getPsTime(halPID); + cpu_time_old = getPsTime(""); + javaProcTAG = String.valueOf(svcmonitor.javaProc.toCharArray()); + } + + @Override + public void run() { + if (halPID.isEmpty() || javaPID.isEmpty()) { + Log.d(javaProcTAG, "No such process: " + + (halPID.isEmpty() ? svcmonitor.halProc : svcmonitor.javaProc)); + return; + } + while (!Thread.interrupted()) { + calculateUsage(); + SystemClock.sleep(period); + } + Log.d(TAG, "Stopping monitor thread"); + } + + private void calculateUsage() { + long java_time = getPsTime(javaPID); + long hal_time = getPsTime(halPID); + long cpu_time = getPsTime(""); + + if (cpu_time_old >= 0) { + float java_diff = (float) (java_time - java_time_old); + float hal_diff = (float) (hal_time - hal_time_old); + float cpu_diff = (float) (cpu_time - cpu_time_old); + Log.w(javaProcTAG, "\n----------------\n"); + Log.w(javaProcTAG, "JAVA level CPU: " + + (java_diff * 100.0 / cpu_diff) + "%\n"); + Log.w(javaProcTAG, " HAL level CPU: " + + (hal_diff * 100.0 / cpu_diff) + "%\n"); + Log.w(javaProcTAG, " SYS level CPU: " + + ((java_diff + hal_diff) * 100.0 / cpu_diff) + "%\n"); + } else { + Log.w(TAG, "Waiting for status\n"); + } + + java_time_old = java_time; + hal_time_old = hal_time; + cpu_time_old = cpu_time; + } + + private String getPIDof(String psName) { + String pid = ""; + + try { + String[] cmd = {"/system/bin/sh", "-c", "ps | grep " + psName}; + Process ps = Runtime.getRuntime().exec(cmd); + BufferedReader in = new BufferedReader( + new InputStreamReader(ps.getInputStream())); + String temp = in.readLine(); + if (temp == null || temp.isEmpty()) + throw new IOException("No such process: " + psName); + pid = temp.split(" +")[1]; + in.close(); + } catch (IOException e) { + Log.d(javaProcTAG, "Error finding PID of process: " + psName + "\n", e); + } + return pid; + } + + private long getPsTime(String pid) { + String psStat = getPsStat("/" + pid); + String[] statBreakDown = psStat.split(" +"); + long psTime; + + if (pid.isEmpty()) { + psTime = Long.parseLong(statBreakDown[1]) + + Long.parseLong(statBreakDown[2]) + + Long.parseLong(statBreakDown[3]) + + Long.parseLong(statBreakDown[4]); + } else { + psTime = Long.parseLong(statBreakDown[13]) + + Long.parseLong(statBreakDown[14]); + } + + return psTime; + } + + private String getPsStat(String psname) { + String stat = ""; + try { + FileInputStream fs = new FileInputStream("/proc" + psname + "/stat"); + BufferedReader br = new BufferedReader(new InputStreamReader(fs)); + stat = br.readLine(); + fs.close(); + } catch (IOException e) { + Log.d(TAG, "Error retreiving stat. \n"); + } + return stat; + } + } +} diff --git a/core/tests/coretests/assets/fonts/HintedAdvanceWidthTest-Regular.ttf b/core/tests/coretests/assets/fonts/HintedAdvanceWidthTest-Regular.ttf Binary files differnew file mode 100644 index 000000000000..1924c35a9c0a --- /dev/null +++ b/core/tests/coretests/assets/fonts/HintedAdvanceWidthTest-Regular.ttf diff --git a/core/tests/coretests/assets/fonts/HintedAdvanceWidthTest-Regular.ttx b/core/tests/coretests/assets/fonts/HintedAdvanceWidthTest-Regular.ttx new file mode 100644 index 000000000000..2b8478d522d2 --- /dev/null +++ b/core/tests/coretests/assets/fonts/HintedAdvanceWidthTest-Regular.ttx @@ -0,0 +1,328 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> + + <GlyphOrder> + <!-- The 'id' attribute is only for humans; it is ignored when parsed. --> + <GlyphID id="0" name=".notdef"/> + <GlyphID id="1" name="space"/> + <GlyphID id="9" name="H"/> + <GlyphID id="16" name="O"/> + </GlyphOrder> + + <head> + <!-- Most of this table will be recalculated by the compiler --> + <tableVersion value="1.0"/> + <fontRevision value="1.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Wed Sep 9 08:01:17 2015"/> + <modified value="Wed Sep 9 08:48:07 2015"/> + <xMin value="30"/> + <yMin value="-200"/> + <xMax value="629"/> + <yMax value="800"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <indexToLocFormat value="0"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="1.0"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <advanceWidthMax value="659"/> + <minLeftSideBearing value="0"/> + <minRightSideBearing value="30"/> + <xMaxExtent value="629"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + <numberOfHMetrics value="18"/> + </hhea> + + <maxp> + <!-- Most of this table will be recalculated by the compiler --> + <tableVersion value="0x10000"/> + <numGlyphs value="54"/> + <maxPoints value="73"/> + <maxContours value="10"/> + <maxCompositePoints value="0"/> + <maxCompositeContours value="0"/> + <maxZones value="2"/> + <maxTwilightPoints value="12"/> + <maxStorage value="28"/> + <maxFunctionDefs value="119"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="61"/> + <maxSizeOfInstructions value="2967"/> + <maxComponentElements value="0"/> + <maxComponentDepth value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="32"/> + <usLastCharIndex value="122"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="H" width="627" lsb="50"/> + <mtx name="O" width="659" lsb="30"/> + <mtx name="space" width="300" lsb="0"/> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <cmap_format_4 platformID="0" platEncID="3" language="0"> + <map code="0x20" name="space"/><!-- SPACE --> + <map code="0x48" name="H"/><!-- LATIN CAPITAL LETTER H --> + <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O --> + </cmap_format_4> + <cmap_format_4 platformID="3" platEncID="1" language="0"> + <map code="0x20" name="space"/><!-- SPACE --> + <map code="0x48" name="H"/><!-- LATIN CAPITAL LETTER H --> + <map code="0x4f" name="O"/><!-- LATIN CAPITAL LETTER O --> + </cmap_format_4> + </cmap> + + <fpgm> + <assembly> + </assembly> + </fpgm> + + <prep> + <assembly> + </assembly> + </prep> + + <cvt> + <cv index="0" value="0"/> + </cvt> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + + <!-- The xMin, yMin, xMax and yMax values + will be recalculated by the compiler. --> + + <TTGlyph name=".notdef" xMin="93" yMin="-200" xMax="410" yMax="800"> + <contour> + <pt x="410" y="0" on="1"/> + <pt x="50" y="0" on="1"/> + <pt x="50" y="700" on="1"/> + <pt x="410" y="700" on="1"/> + </contour> + <instructions><assembly> + PUSHB[] 1 2 4 3 + SLOOP[] + PUSH[] -400 + SHPIX[] + + PUSHB[] 0 3 5 3 + SLOOP[] + PUSH[] 400 + SHPIX[] + </assembly></instructions> + </TTGlyph> + + <TTGlyph name="H" xMin="50" yMin="0" xMax="577" yMax="700"> + <contour> + <pt x="50" y="700" on="1"/> + <pt x="200" y="700" on="1"/> + <pt x="200" y="447" on="1"/> + <pt x="427" y="447" on="1"/> + <pt x="427" y="700" on="1"/> + <pt x="577" y="700" on="1"/> + <pt x="577" y="0" on="1"/> + <pt x="427" y="0" on="1"/> + <pt x="427" y="297" on="1"/> + <pt x="200" y="297" on="1"/> + <pt x="200" y="0" on="1"/> + <pt x="50" y="0" on="1"/> + </contour> + <instructions><assembly> + PUSHB[] 0 11 12 3 + SLOOP[] + PUSH[] -200 + SHPIX[] + PUSHB[] 5 6 13 3 + SLOOP[] + PUSH[] 200 + SHPIX[] + </assembly></instructions> + </TTGlyph> + + <TTGlyph name="O" xMin="30" yMin="0" xMax="629" yMax="700"> + <contour> + <pt x="248" y="0" on="0"/> + <pt x="111" y="94" on="0"/> + <pt x="30" y="255" on="0"/> + <pt x="30" y="350" on="1"/> + <pt x="30" y="445" on="0"/> + <pt x="111" y="606" on="0"/> + <pt x="248" y="700" on="0"/> + <pt x="330" y="700" on="1"/> + <pt x="411" y="700" on="0"/> + <pt x="548" y="606" on="0"/> + <pt x="629" y="445" on="0"/> + <pt x="629" y="350" on="1"/> + <pt x="629" y="255" on="0"/> + <pt x="548" y="94" on="0"/> + <pt x="411" y="0" on="0"/> + <pt x="330" y="0" on="1"/> + </contour> + <contour> + <pt x="370" y="150" on="0"/> + <pt x="439" y="209" on="0"/> + <pt x="480" y="302" on="0"/> + <pt x="480" y="350" on="1"/> + <pt x="480" y="398" on="0"/> + <pt x="439" y="491" on="0"/> + <pt x="370" y="550" on="0"/> + <pt x="330" y="550" on="1"/> + <pt x="289" y="550" on="0"/> + <pt x="220" y="491" on="0"/> + <pt x="179" y="398" on="0"/> + <pt x="179" y="350" on="1"/> + <pt x="179" y="302" on="0"/> + <pt x="220" y="209" on="0"/> + <pt x="289" y="150" on="0"/> + <pt x="330" y="150" on="1"/> + </contour> + <instructions><assembly> + PUSH[] 32 -200 + SHPIX[] + PUSHB[] 33 200 + SHPIX[] + </assembly></instructions> + </TTGlyph> + + <TTGlyph name="space"> + <contour></contour> + <instructions><assembly> + PUSH[] 0 -200 + SHPIX[] + PUSHB[] 1 200 + SHPIX[] + </assembly></instructions> + </TTGlyph> + + </glyf> + + <name> + <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True"> + Hinted Advance Width Test + </namerecord> + <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True"> + Regular + </namerecord> + <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True"> + Hinted Advance Width Test + </namerecord> + <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True"> + HintedAdvanceWidthTest-Regular + </namerecord> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + Hinted Advance Width Test + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Regular + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + Hinted Advance Width Test + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + HintedAdvanceWidthTest-Regular + </namerecord> + </name> + + <post> + <formatType value="2.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + <psNames> + <!-- This file uses unique glyph names based on the information + found in the 'post' table. Since these names might not be unique, + we have to invent artificial names in case of clashes. In order to + be able to retain the original information, we need a name to + ps name mapping for those cases where they differ. That's what + you see below. + --> + </psNames> + <extraNames> + <!-- following are the name that are not taken from the standard Mac glyph order --> + </extraNames> + </post> + + <gasp> + <gaspRange rangeMaxPPEM="65535" rangeGaspBehavior="15"/> + </gasp> + +</ttFont> diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java new file mode 100644 index 000000000000..e97bb33487c4 --- /dev/null +++ b/core/tests/coretests/src/android/graphics/PaintTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import android.graphics.Paint; +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +/** + * PaintTest tests {@link Paint}. + */ +public class PaintTest extends InstrumentationTestCase { + private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf"; + + static void assertEquals(String message, float[] expected, float[] actual) { + if (expected.length != actual.length) { + fail(message + " expected array length:<" + expected.length + "> but was:<" + + actual.length + ">"); + } + for (int i = 0; i < expected.length; ++i) { + if (expected[i] != actual[i]) { + fail(message + " expected array element[" +i + "]:<" + expected[i] + ">but was:<" + + actual[i] + ">"); + } + } + } + + static class HintingTestCase { + public final String mText; + public final float mTextSize; + public final float[] mWidthWithoutHinting; + public final float[] mWidthWithHinting; + + public HintingTestCase(String text, float textSize, float[] widthWithoutHinting, + float[] widthWithHinting) { + mText = text; + mTextSize = textSize; + mWidthWithoutHinting = widthWithoutHinting; + mWidthWithHinting = widthWithHinting; + } + } + + // Following test cases are only valid for HintedAdvanceWidthTest-Regular.ttf in assets/fonts. + HintingTestCase[] HINTING_TESTCASES = { + new HintingTestCase("H", 11f, new float[] { 7f }, new float[] { 13f }), + new HintingTestCase("O", 11f, new float[] { 7f }, new float[] { 13f }), + + new HintingTestCase("H", 13f, new float[] { 8f }, new float[] { 14f }), + new HintingTestCase("O", 13f, new float[] { 9f }, new float[] { 15f }), + + new HintingTestCase("HO", 11f, new float[] { 7f, 7f }, new float[] { 13f, 13f }), + new HintingTestCase("OH", 11f, new float[] { 7f, 7f }, new float[] { 13f, 13f }), + + new HintingTestCase("HO", 13f, new float[] { 8f, 9f }, new float[] { 14f, 15f }), + new HintingTestCase("OH", 13f, new float[] { 9f, 8f }, new float[] { 15f, 14f }), + }; + + @SmallTest + public void testHintingWidth() { + final Typeface fontTypeface = Typeface.createFromAsset( + getInstrumentation().getContext().getAssets(), FONT_PATH); + Paint paint = new Paint(); + paint.setTypeface(fontTypeface); + + for (int i = 0; i < HINTING_TESTCASES.length; ++i) { + HintingTestCase testCase = HINTING_TESTCASES[i]; + + paint.setTextSize(testCase.mTextSize); + + float[] widths = new float[testCase.mText.length()]; + + paint.setHinting(Paint.HINTING_OFF); + paint.getTextWidths(String.valueOf(testCase.mText), widths); + assertEquals("Text width of '" + testCase.mText + "' without hinting is not expected.", + testCase.mWidthWithoutHinting, widths); + + paint.setHinting(Paint.HINTING_ON); + paint.getTextWidths(String.valueOf(testCase.mText), widths); + assertEquals("Text width of '" + testCase.mText + "' with hinting is not expected.", + testCase.mWidthWithHinting, widths); + } + } +} diff --git a/core/tests/coretests/src/android/text/method/WordIteratorTest.java b/core/tests/coretests/src/android/text/method/WordIteratorTest.java new file mode 100644 index 000000000000..37f887cd310e --- /dev/null +++ b/core/tests/coretests/src/android/text/method/WordIteratorTest.java @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text.method; + +import android.test.AndroidTestCase; + +import java.text.BreakIterator; +import java.util.Locale; + +// TODO(Bug: 24062099): Add more tests for non-ascii text. +public class WordIteratorTest extends AndroidTestCase { + + public void testSetCharSequence() { + final String text = "text"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + + try { + wordIterator.setCharSequence(text, 100, 100); + fail("setCharSequence with invalid start and end values should throw " + + "IndexOutOfBoundsException."); + } catch (IndexOutOfBoundsException e) { + } + try { + wordIterator.setCharSequence(text, -100, -100); + fail("setCharSequence with invalid start and end values should throw " + + "IndexOutOfBoundsException."); + } catch (IndexOutOfBoundsException e) { + } + + wordIterator.setCharSequence(text, 0, text.length()); + wordIterator.setCharSequence(text, 0, 0); + wordIterator.setCharSequence(text, text.length(), text.length()); + } + + public void testPreceding() { + final String text = "abc def-ghi. jkl"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + try { + wordIterator.preceding(-1); + fail("preceding with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.preceding(text.length() + 1); + fail("preceding with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + + assertEquals(BreakIterator.DONE, wordIterator.preceding(text.indexOf('a'))); + assertEquals(text.indexOf('a'), wordIterator.preceding(text.indexOf('c'))); + assertEquals(text.indexOf('a'), wordIterator.preceding(text.indexOf('d'))); + assertEquals(text.indexOf('d'), wordIterator.preceding(text.indexOf('e'))); + assertEquals(text.indexOf('d'), wordIterator.preceding(text.indexOf('g'))); + assertEquals(text.indexOf('g'), wordIterator.preceding(text.indexOf('h'))); + assertEquals(text.indexOf('g'), wordIterator.preceding(text.indexOf('j'))); + assertEquals(text.indexOf('j'), wordIterator.preceding(text.indexOf('l'))); + } + + public void testFollowing() { + final String text = "abc def-ghi. jkl"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + try { + wordIterator.following(-1); + fail("following with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.following(text.length() + 1); + fail("following with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + + assertEquals(text.indexOf('c') + 1, wordIterator.following(text.indexOf('a'))); + assertEquals(text.indexOf('c') + 1, wordIterator.following(text.indexOf('c'))); + assertEquals(text.indexOf('f') + 1, wordIterator.following(text.indexOf('c') + 1)); + assertEquals(text.indexOf('f') + 1, wordIterator.following(text.indexOf('d'))); + assertEquals(text.indexOf('i') + 1, wordIterator.following(text.indexOf('-'))); + assertEquals(text.indexOf('i') + 1, wordIterator.following(text.indexOf('g'))); + assertEquals(text.length(), wordIterator.following(text.indexOf('j'))); + assertEquals(BreakIterator.DONE, wordIterator.following(text.length())); + } + + public void testIsBoundary() { + final String text = "abc def-ghi. jkl"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + try { + wordIterator.isBoundary(-1); + fail("isBoundary with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.isBoundary(text.length() + 1); + fail("isBoundary with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + + assertTrue(wordIterator.isBoundary(text.indexOf('a'))); + assertFalse(wordIterator.isBoundary(text.indexOf('b'))); + assertTrue(wordIterator.isBoundary(text.indexOf('c') + 1)); + assertTrue(wordIterator.isBoundary(text.indexOf('d'))); + assertTrue(wordIterator.isBoundary(text.indexOf('-'))); + assertTrue(wordIterator.isBoundary(text.indexOf('g'))); + assertTrue(wordIterator.isBoundary(text.indexOf('.'))); + assertTrue(wordIterator.isBoundary(text.indexOf('j'))); + assertTrue(wordIterator.isBoundary(text.length())); + } + + public void testNextBoundary() { + final String text = "abc def-ghi. jkl"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + try { + wordIterator.nextBoundary(-1); + fail("nextBoundary with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.nextBoundary(text.length() + 1); + fail("nextBoundary with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + + + int currentOffset = 0; + currentOffset = wordIterator.nextBoundary(currentOffset); + assertEquals(text.indexOf('c') + 1, currentOffset); + + currentOffset = wordIterator.nextBoundary(currentOffset); + assertEquals(text.indexOf('d'), currentOffset); + + currentOffset = wordIterator.nextBoundary(currentOffset); + assertEquals(text.indexOf('f') + 1, currentOffset); + + currentOffset = wordIterator.nextBoundary(currentOffset); + assertEquals(text.indexOf('g'), currentOffset); + + currentOffset = wordIterator.nextBoundary(currentOffset); + assertEquals(text.indexOf('i') + 1, currentOffset); + + currentOffset = wordIterator.nextBoundary(currentOffset); + assertEquals(text.indexOf('.') + 1, currentOffset); + + currentOffset = wordIterator.nextBoundary(currentOffset); + assertEquals(text.indexOf('j'), currentOffset); + + currentOffset = wordIterator.nextBoundary(currentOffset); + assertEquals(text.length(), currentOffset); + + currentOffset = wordIterator.nextBoundary(currentOffset); + assertEquals(BreakIterator.DONE, currentOffset); + } + + public void testPrevBoundary() { + final String text = "abc def-ghi. jkl"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + try { + wordIterator.prevBoundary(-1); + fail("prevBoundary with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.prevBoundary(text.length() + 1); + fail("prevBoundary with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + + int currentOffset = text.length(); + currentOffset = wordIterator.prevBoundary(currentOffset); + assertEquals(text.indexOf('j'), currentOffset); + + currentOffset = wordIterator.prevBoundary(currentOffset); + assertEquals(text.indexOf('.') + 1, currentOffset); + + currentOffset = wordIterator.prevBoundary(currentOffset); + assertEquals(text.indexOf('i') + 1, currentOffset); + + currentOffset = wordIterator.prevBoundary(currentOffset); + assertEquals(text.indexOf('g'), currentOffset); + + currentOffset = wordIterator.prevBoundary(currentOffset); + assertEquals(text.indexOf('f') + 1, currentOffset); + + currentOffset = wordIterator.prevBoundary(currentOffset); + assertEquals(text.indexOf('d'), currentOffset); + + currentOffset = wordIterator.prevBoundary(currentOffset); + assertEquals(text.indexOf('c') + 1, currentOffset); + + currentOffset = wordIterator.prevBoundary(currentOffset); + assertEquals(text.indexOf('a'), currentOffset); + + currentOffset = wordIterator.prevBoundary(currentOffset); + assertEquals(BreakIterator.DONE, currentOffset); + } + + public void testGetBeginning() { + { + final String text = "abc def-ghi. jkl"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + try { + wordIterator.getBeginning(-1); + fail("getBeginning with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.getBeginning(text.length() + 1); + fail("getBeginning with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.getPrevWordBeginningOnTwoWordsBoundary(-1); + fail("getPrevWordBeginningOnTwoWordsBoundary with invalid offset should throw " + + "IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.getPrevWordBeginningOnTwoWordsBoundary(text.length() + 1); + fail("getPrevWordBeginningOnTwoWordsBoundary with invalid offset should throw " + + "IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + } + + { + final String text = "abc def-ghi. jkl"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + assertEquals(text.indexOf('a'), wordIterator.getBeginning(text.indexOf('a'))); + assertEquals(text.indexOf('a'), wordIterator.getBeginning(text.indexOf('c'))); + assertEquals(text.indexOf('a'), wordIterator.getBeginning(text.indexOf('c') + 1)); + assertEquals(text.indexOf('d'), wordIterator.getBeginning(text.indexOf('d'))); + assertEquals(text.indexOf('d'), wordIterator.getBeginning(text.indexOf('-'))); + assertEquals(text.indexOf('g'), wordIterator.getBeginning(text.indexOf('g'))); + assertEquals(text.indexOf('g'), wordIterator.getBeginning(text.indexOf('.'))); + assertEquals(BreakIterator.DONE, wordIterator.getBeginning(text.indexOf('.') + 1)); + assertEquals(text.indexOf('j'), wordIterator.getBeginning(text.indexOf('j'))); + assertEquals(text.indexOf('j'), wordIterator.getBeginning(text.indexOf('l') + 1)); + + for (int i = 0; i < text.length(); i++) { + assertEquals(wordIterator.getBeginning(i), + wordIterator.getPrevWordBeginningOnTwoWordsBoundary(i)); + } + } + + { + // Japanese HIRAGANA letter + KATAKANA letters + final String text = "\u3042\u30A2\u30A3\u30A4"; + WordIterator wordIterator = new WordIterator(Locale.JAPANESE); + wordIterator.setCharSequence(text, 0, text.length()); + + assertEquals(text.indexOf('\u3042'), wordIterator.getBeginning(text.indexOf('\u3042'))); + assertEquals(text.indexOf('\u30A2'), wordIterator.getBeginning(text.indexOf('\u30A2'))); + assertEquals(text.indexOf('\u30A2'), wordIterator.getBeginning(text.indexOf('\u30A4'))); + assertEquals(text.indexOf('\u30A2'), wordIterator.getBeginning(text.length())); + + assertEquals(text.indexOf('\u3042'), + wordIterator.getPrevWordBeginningOnTwoWordsBoundary(text.indexOf('\u3042'))); + assertEquals(text.indexOf('\u3042'), + wordIterator.getPrevWordBeginningOnTwoWordsBoundary(text.indexOf('\u30A2'))); + assertEquals(text.indexOf('\u30A2'), + wordIterator.getPrevWordBeginningOnTwoWordsBoundary(text.indexOf('\u30A4'))); + assertEquals(text.indexOf('\u30A2'), + wordIterator.getPrevWordBeginningOnTwoWordsBoundary(text.length())); + } + } + + public void testGetEnd() { + { + final String text = "abc def-ghi. jkl"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + try { + wordIterator.getEnd(-1); + fail("getEnd with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.getEnd(text.length() + 1); + fail("getEnd with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.getNextWordEndOnTwoWordBoundary(-1); + fail("getNextWordEndOnTwoWordBoundary with invalid offset should throw " + + "IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.getNextWordEndOnTwoWordBoundary(text.length() + 1); + fail("getNextWordEndOnTwoWordBoundary with invalid offset should throw " + + "IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + } + + { + final String text = "abc def-ghi. jkl"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + assertEquals(text.indexOf('c') + 1, wordIterator.getEnd(text.indexOf('a'))); + assertEquals(text.indexOf('c') + 1, wordIterator.getEnd(text.indexOf('c'))); + assertEquals(text.indexOf('c') + 1, wordIterator.getEnd(text.indexOf('c') + 1)); + assertEquals(text.indexOf('f') + 1, wordIterator.getEnd(text.indexOf('d'))); + assertEquals(text.indexOf('f') + 1, wordIterator.getEnd(text.indexOf('f') + 1)); + assertEquals(text.indexOf('i') + 1, wordIterator.getEnd(text.indexOf('g'))); + assertEquals(text.indexOf('i') + 1, wordIterator.getEnd(text.indexOf('i') + 1)); + assertEquals(BreakIterator.DONE, wordIterator.getEnd(text.indexOf('.') + 1)); + assertEquals(text.indexOf('l') + 1, wordIterator.getEnd(text.indexOf('j'))); + assertEquals(text.indexOf('l') + 1, wordIterator.getEnd(text.indexOf('l') + 1)); + + for (int i = 0; i < text.length(); i++) { + assertEquals(wordIterator.getEnd(i), + wordIterator.getNextWordEndOnTwoWordBoundary(i)); + } + } + + { + // Japanese HIRAGANA letter + KATAKANA letters + final String text = "\u3042\u30A2\u30A3\u30A4"; + WordIterator wordIterator = new WordIterator(Locale.JAPANESE); + wordIterator.setCharSequence(text, 0, text.length()); + + assertEquals(text.indexOf('\u3042') + 1, wordIterator.getEnd(text.indexOf('\u3042'))); + assertEquals(text.indexOf('\u3042') + 1, wordIterator.getEnd(text.indexOf('\u30A2'))); + assertEquals(text.indexOf('\u30A4') + 1, wordIterator.getEnd(text.indexOf('\u30A4'))); + assertEquals(text.indexOf('\u30A4') + 1, + wordIterator.getEnd(text.indexOf('\u30A4') + 1)); + + assertEquals(text.indexOf('\u3042') + 1, + wordIterator.getNextWordEndOnTwoWordBoundary(text.indexOf('\u3042'))); + assertEquals(text.indexOf('\u30A4') + 1, + wordIterator.getNextWordEndOnTwoWordBoundary(text.indexOf('\u30A2'))); + assertEquals(text.indexOf('\u30A4') + 1, + wordIterator.getNextWordEndOnTwoWordBoundary(text.indexOf('\u30A4'))); + assertEquals(text.indexOf('\u30A4') + 1, + wordIterator.getNextWordEndOnTwoWordBoundary(text.indexOf('\u30A4') + 1)); + } + } + + public void testGetPunctuationBeginning() { + final String text = "abc!? (^^;) def"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + // TODO: Shouldn't this throw an exception? + assertEquals(BreakIterator.DONE, wordIterator.getPunctuationBeginning(BreakIterator.DONE)); + + try { + wordIterator.getPunctuationBeginning(-2); + fail("getPunctuationBeginning with invalid offset should throw " + + "IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.getPunctuationBeginning(text.length() + 1); + fail("getPunctuationBeginning with invalid offset should throw " + + "IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + + assertEquals(BreakIterator.DONE, wordIterator.getPunctuationBeginning(text.indexOf('a'))); + assertEquals(BreakIterator.DONE, wordIterator.getPunctuationBeginning(text.indexOf('c'))); + assertEquals(text.indexOf('!'), wordIterator.getPunctuationBeginning(text.indexOf('!'))); + assertEquals(text.indexOf('!'), + wordIterator.getPunctuationBeginning(text.indexOf('?') + 1)); + assertEquals(text.indexOf(';'), wordIterator.getPunctuationBeginning(text.indexOf(';'))); + assertEquals(text.indexOf(';'), wordIterator.getPunctuationBeginning(text.indexOf(')'))); + assertEquals(text.indexOf(';'), wordIterator.getPunctuationBeginning(text.length())); + } + + public void testGetPunctuationEnd() { + final String text = "abc!? (^^;) def"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + // TODO: Shouldn't this throw an exception? + assertEquals(BreakIterator.DONE, wordIterator.getPunctuationEnd(BreakIterator.DONE)); + + try { + wordIterator.getPunctuationEnd(-2); + fail("getPunctuationEnd with invalid offset should throw IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + try { + wordIterator.getPunctuationEnd(text.length() + 1); + fail("getPunctuationBeginning with invalid offset should throw " + + "IllegalArgumentException."); + } catch (IllegalArgumentException e) { + } + + assertEquals(text.indexOf('?') + 1, wordIterator.getPunctuationEnd(text.indexOf('a'))); + assertEquals(text.indexOf('?') + 1, wordIterator.getPunctuationEnd(text.indexOf('?') + 1)); + assertEquals(text.indexOf('(') + 1, wordIterator.getPunctuationEnd(text.indexOf('('))); + assertEquals(text.indexOf(')') + 1, wordIterator.getPunctuationEnd(text.indexOf('(') + 2)); + assertEquals(text.indexOf(')') + 1, wordIterator.getPunctuationEnd(text.indexOf(')') + 1)); + assertEquals(BreakIterator.DONE, wordIterator.getPunctuationEnd(text.indexOf('d'))); + assertEquals(BreakIterator.DONE, wordIterator.getPunctuationEnd(text.length())); + } + + public void testIsAfterPunctuation() { + final String text = "abc!? (^^;) def"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + assertFalse(wordIterator.isAfterPunctuation(text.indexOf('a'))); + assertFalse(wordIterator.isAfterPunctuation(text.indexOf('!'))); + assertTrue(wordIterator.isAfterPunctuation(text.indexOf('?'))); + assertTrue(wordIterator.isAfterPunctuation(text.indexOf('?') + 1)); + assertFalse(wordIterator.isAfterPunctuation(text.indexOf('d'))); + + assertFalse(wordIterator.isAfterPunctuation(BreakIterator.DONE)); + assertFalse(wordIterator.isAfterPunctuation(text.length() + 1)); + } + + public void testIsOnPunctuation() { + final String text = "abc!? (^^;) def"; + WordIterator wordIterator = new WordIterator(Locale.ENGLISH); + wordIterator.setCharSequence(text, 0, text.length()); + + assertFalse(wordIterator.isOnPunctuation(text.indexOf('a'))); + assertTrue(wordIterator.isOnPunctuation(text.indexOf('!'))); + assertTrue(wordIterator.isOnPunctuation(text.indexOf('?'))); + assertFalse(wordIterator.isOnPunctuation(text.indexOf('?') + 1)); + assertTrue(wordIterator.isOnPunctuation(text.indexOf(')'))); + assertFalse(wordIterator.isOnPunctuation(text.indexOf(')') + 1)); + assertFalse(wordIterator.isOnPunctuation(text.indexOf('d'))); + + assertFalse(wordIterator.isOnPunctuation(BreakIterator.DONE)); + assertFalse(wordIterator.isOnPunctuation(text.length())); + assertFalse(wordIterator.isOnPunctuation(text.length() + 1)); + } +} diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index 54117df35a01..b37688f0e212 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -16,8 +16,10 @@ package android.widget; +import static android.widget.espresso.TextViewActions.clickOnTextAtIndex; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.pressKey; import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.withId; @@ -27,6 +29,7 @@ import com.android.frameworks.coretests.R; import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.SmallTest; +import android.view.KeyEvent; /** * Tests the TextView widget from an Activity @@ -47,4 +50,17 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV onView(withId(R.id.textview)).check(matches(withText(helloWorld))); } + + @SmallTest + public void testPositionCursorAtTextAtIndex() throws Exception { + getActivity(); + + final String helloWorld = "Hello world!"; + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld)); + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("world"))); + + // Delete text at specified index and see if we got the right one. + onView(withId(R.id.textview)).perform(pressKey(KeyEvent.KEYCODE_FORWARD_DEL)); + onView(withId(R.id.textview)).check(matches(withText("Hello orld!"))); + } } diff --git a/core/tests/coretests/src/android/widget/TextViewWordLimitsTest.java b/core/tests/coretests/src/android/widget/TextViewWordLimitsTest.java deleted file mode 100644 index 4b6616430e43..000000000000 --- a/core/tests/coretests/src/android/widget/TextViewWordLimitsTest.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ - -package android.widget; - -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.Suppress; -import android.text.InputType; -import android.text.Selection; -import android.text.Spannable; -import android.text.SpannableString; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * TextViewPatchTest tests {@link TextView}'s definition of word. Finds and - * verifies word limits to be in strings containing different kinds of - * characters. - */ -@Suppress // Failing. -public class TextViewWordLimitsTest extends AndroidTestCase { - - TextView mTv = null; - Method mGetWordLimits, mSelectCurrentWord; - Field mContextMenuTriggeredByKey, mSelectionControllerEnabled; - - - /** - * Sets up common fields used in all test cases. - * @throws NoSuchFieldException - * @throws SecurityException - */ - @Override - protected void setUp() throws NoSuchMethodException, SecurityException, NoSuchFieldException { - mTv = new TextView(getContext()); - mTv.setInputType(InputType.TYPE_CLASS_TEXT); - - mGetWordLimits = mTv.getClass().getDeclaredMethod("getWordLimitsAt", - new Class[] {int.class}); - mGetWordLimits.setAccessible(true); - - mSelectCurrentWord = mTv.getClass().getDeclaredMethod("selectCurrentWord", new Class[] {}); - mSelectCurrentWord.setAccessible(true); - - mContextMenuTriggeredByKey = mTv.getClass().getDeclaredField("mContextMenuTriggeredByKey"); - mContextMenuTriggeredByKey.setAccessible(true); - mSelectionControllerEnabled = mTv.getClass().getDeclaredField("mSelectionControllerEnabled"); - mSelectionControllerEnabled.setAccessible(true); - } - - /** - * Calculate and verify word limits. Depends on the TextView implementation. - * Uses a private method and internal data representation. - * - * @param text Text to select a word from - * @param pos Position to expand word around - * @param correctStart Correct start position for the word - * @param correctEnd Correct end position for the word - * @throws InvocationTargetException - * @throws IllegalAccessException - * @throws IllegalArgumentException - * @throws InvocationTargetException - * @throws IllegalAccessException - */ - private void verifyWordLimits(String text, int pos, int correctStart, int correctEnd) - throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { - mTv.setText(text, TextView.BufferType.SPANNABLE); - - long limits = (Long)mGetWordLimits.invoke(mTv, new Object[] {new Integer(pos)}); - int actualStart = (int)(limits >>> 32); - int actualEnd = (int)(limits & 0x00000000FFFFFFFFL); - assertEquals(correctStart, actualStart); - assertEquals(correctEnd, actualEnd); - } - - - private void verifySelectCurrentWord(Spannable text, int selectionStart, int selectionEnd, int correctStart, - int correctEnd) throws InvocationTargetException, IllegalAccessException { - mTv.setText(text, TextView.BufferType.SPANNABLE); - - Selection.setSelection((Spannable)mTv.getText(), selectionStart, selectionEnd); - mContextMenuTriggeredByKey.setBoolean(mTv, true); - mSelectionControllerEnabled.setBoolean(mTv, true); - mSelectCurrentWord.invoke(mTv); - - assertEquals(correctStart, mTv.getSelectionStart()); - assertEquals(correctEnd, mTv.getSelectionEnd()); - } - - - /** - * Corner cases for string length. - */ - @LargeTest - public void testLengths() throws Exception { - final String ONE_TWO = "one two"; - final String EMPTY = ""; - final String TOOLONG = "ThisWordIsTooLongToBeDefinedAsAWordInTheSenseUsedInTextView"; - - // Select first word - verifyWordLimits(ONE_TWO, 0, 0, 3); - verifyWordLimits(ONE_TWO, 3, 0, 3); - - // Select last word - verifyWordLimits(ONE_TWO, 4, 4, 7); - verifyWordLimits(ONE_TWO, 7, 4, 7); - - // Empty string - verifyWordLimits(EMPTY, 0, -1, -1); - - // Too long word - verifyWordLimits(TOOLONG, 0, -1, -1); - } - - /** - * Unicode classes included. - */ - @LargeTest - public void testIncludedClasses() throws Exception { - final String LOWER = "njlj"; - final String UPPER = "NJLJ"; - final String TITLECASE = "\u01C8\u01CB\u01F2"; // Lj Nj Dz - final String OTHER = "\u3042\u3044\u3046"; // Hiragana AIU - final String MODIFIER = "\u02C6\u02CA\u02CB"; // Circumflex Acute Grave - - // Each string contains a single valid word - verifyWordLimits(LOWER, 1, 0, 4); - verifyWordLimits(UPPER, 1, 0, 4); - verifyWordLimits(TITLECASE, 1, 0, 3); - verifyWordLimits(OTHER, 1, 0, 3); - verifyWordLimits(MODIFIER, 1, 0, 3); - } - - /** - * Unicode classes included if combined with a letter. - */ - @LargeTest - public void testPartlyIncluded() throws Exception { - final String NUMBER = "123"; - final String NUMBER_LOWER = "1st"; - final String APOSTROPHE = "''"; - final String APOSTROPHE_LOWER = "'Android's'"; - - // Pure decimal number is ignored - verifyWordLimits(NUMBER, 1, -1, -1); - - // Number with letter is valid - verifyWordLimits(NUMBER_LOWER, 1, 0, 3); - - // Stand apostrophes are ignore - verifyWordLimits(APOSTROPHE, 1, -1, -1); - - // Apostrophes are accepted if they are a part of a word - verifyWordLimits(APOSTROPHE_LOWER, 1, 0, 11); - } - - /** - * Unicode classes included if combined with a letter. - */ - @LargeTest - public void testFinalSeparator() throws Exception { - final String PUNCTUATION = "abc, def."; - - // Starting from the comma - verifyWordLimits(PUNCTUATION, 3, 0, 3); - verifyWordLimits(PUNCTUATION, 4, 0, 4); - - // Starting from the final period - verifyWordLimits(PUNCTUATION, 8, 5, 8); - verifyWordLimits(PUNCTUATION, 9, 5, 9); - } - - /** - * Unicode classes other than listed in testIncludedClasses and - * testPartlyIncluded act as word separators. - */ - @LargeTest - public void testNotIncluded() throws Exception { - // Selection of character classes excluded - final String MARK_NONSPACING = "a\u030A"; // a Combining ring above - final String PUNCTUATION_OPEN_CLOSE = "(c)"; // Parenthesis - final String PUNCTUATION_DASH = "non-fiction"; // Hyphen - final String PUNCTUATION_OTHER = "b&b"; // Ampersand - final String SYMBOL_OTHER = "Android\u00AE"; // Registered - final String SEPARATOR_SPACE = "one two"; // Space - - // "a" - verifyWordLimits(MARK_NONSPACING, 1, 0, 1); - - // "c" - verifyWordLimits(PUNCTUATION_OPEN_CLOSE, 1, 1, 2); - - // "non-" - verifyWordLimits(PUNCTUATION_DASH, 3, 0, 3); - verifyWordLimits(PUNCTUATION_DASH, 4, 4, 11); - - // "b" - verifyWordLimits(PUNCTUATION_OTHER, 0, 0, 1); - verifyWordLimits(PUNCTUATION_OTHER, 1, 0, 1); - verifyWordLimits(PUNCTUATION_OTHER, 2, 0, 3); // & is considered a punctuation sign. - verifyWordLimits(PUNCTUATION_OTHER, 3, 2, 3); - - // "Android" - verifyWordLimits(SYMBOL_OTHER, 7, 0, 7); - verifyWordLimits(SYMBOL_OTHER, 8, -1, -1); - - // "one" - verifyWordLimits(SEPARATOR_SPACE, 1, 0, 3); - } - - /** - * Surrogate characters are treated as their code points. - */ - @LargeTest - public void testSurrogate() throws Exception { - final String SURROGATE_LETTER = "\uD800\uDC00\uD800\uDC01\uD800\uDC02"; // Linear B AEI - final String SURROGATE_SYMBOL = "\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03"; // Three smileys - - // Letter Other is included even when coded as surrogate pairs - verifyWordLimits(SURROGATE_LETTER, 0, 0, 6); - verifyWordLimits(SURROGATE_LETTER, 1, 0, 6); - verifyWordLimits(SURROGATE_LETTER, 2, 0, 6); - verifyWordLimits(SURROGATE_LETTER, 3, 0, 6); - verifyWordLimits(SURROGATE_LETTER, 4, 0, 6); - verifyWordLimits(SURROGATE_LETTER, 5, 0, 6); - verifyWordLimits(SURROGATE_LETTER, 6, 0, 6); - - // Not included classes are ignored even when coded as surrogate pairs - verifyWordLimits(SURROGATE_SYMBOL, 0, -1, -1); - verifyWordLimits(SURROGATE_SYMBOL, 1, -1, -1); - verifyWordLimits(SURROGATE_SYMBOL, 2, -1, -1); - verifyWordLimits(SURROGATE_SYMBOL, 3, -1, -1); - verifyWordLimits(SURROGATE_SYMBOL, 4, -1, -1); - verifyWordLimits(SURROGATE_SYMBOL, 5, -1, -1); - verifyWordLimits(SURROGATE_SYMBOL, 6, -1, -1); - } - - /** - * Selection is used if present and valid word. - */ - @LargeTest - public void testSelectCurrentWord() throws Exception { - SpannableString textLower = new SpannableString("first second"); - SpannableString textOther = new SpannableString("\u3042\3044\3046"); // Hiragana AIU - SpannableString textDash = new SpannableString("non-fiction"); // Hyphen - SpannableString textPunctOther = new SpannableString("b&b"); // Ampersand - SpannableString textSymbolOther = new SpannableString("Android\u00AE"); // Registered - - // Valid selection - Letter, Lower - verifySelectCurrentWord(textLower, 2, 5, 0, 5); - - // Adding the space spreads to the second word - verifySelectCurrentWord(textLower, 2, 6, 0, 12); - - // Valid selection -- Letter, Other - verifySelectCurrentWord(textOther, 1, 2, 0, 5); - - // Zero-width selection is interpreted as a cursor and the selection is ignored - verifySelectCurrentWord(textLower, 2, 2, 0, 5); - - // Hyphen is part of selection - verifySelectCurrentWord(textDash, 2, 5, 0, 11); - - // Ampersand part of selection or not - verifySelectCurrentWord(textPunctOther, 1, 2, 0, 3); - verifySelectCurrentWord(textPunctOther, 1, 3, 0, 3); - - // (R) part of the selection - verifySelectCurrentWord(textSymbolOther, 2, 7, 0, 7); - verifySelectCurrentWord(textSymbolOther, 2, 8, 0, 8); - } -} diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java new file mode 100644 index 000000000000..425dccdf4936 --- /dev/null +++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.widget.espresso; + +import static android.support.test.espresso.action.ViewActions.actionWithAssertions; + +import android.content.res.Resources; +import android.support.test.espresso.PerformException; +import android.support.test.espresso.ViewAction; +import android.support.test.espresso.action.CoordinatesProvider; +import android.support.test.espresso.action.GeneralClickAction; +import android.support.test.espresso.action.Press; +import android.support.test.espresso.action.Tap; +import android.support.test.espresso.util.HumanReadables; +import android.text.Layout; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; +import android.widget.TextView; + +/** + * A collection of actions on a {@link android.widget.TextView}. + */ +public final class TextViewActions { + + private TextViewActions() {} + + /** + * Returns an action that clicks on text at an index on the text view.<br> + * <br> + * View constraints: + * <ul> + * <li>must be a text view displayed on screen + * <ul> + */ + public static ViewAction clickOnTextAtIndex(int index) { + return actionWithAssertions( + new GeneralClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER)); + } + + /** + * A provider of the x, y coordinates of the text at the specified index in a text view. + */ + private static final class TextCoordinates implements CoordinatesProvider { + + private final int mIndex; + private final String mActionDescription; + + public TextCoordinates(int index) { + mIndex = index; + mActionDescription = "Could not locate text at index: " + mIndex; + } + + @Override + public float[] calculateCoordinates(View view) { + try { + return locateTextAtIndex((TextView) view, mIndex); + } catch (ClassCastException e) { + throw new PerformException.Builder() + .withActionDescription(mActionDescription) + .withViewDescription(HumanReadables.describe(view)) + .withCause(e) + .build(); + } catch (StringIndexOutOfBoundsException e) { + throw new PerformException.Builder() + .withActionDescription(mActionDescription) + .withViewDescription(HumanReadables.describe(view)) + .withCause(e) + .build(); + } + } + + /** + * @throws StringIndexOutOfBoundsException + */ + private float[] locateTextAtIndex(TextView textView, int index) { + if (index < 0 || index > textView.getText().length()) { + throw new StringIndexOutOfBoundsException(index); + } + final int[] xy = new int[2]; + textView.getLocationOnScreen(xy); + final Layout layout = textView.getLayout(); + final int line = layout.getLineForOffset(index); + final float x = textView.getTotalPaddingLeft() - textView.getScrollX() + + layout.getPrimaryHorizontal(index); + final float y = textView.getTotalPaddingTop() - textView.getScrollY() + + layout.getLineTop(line); + return new float[]{x + xy[0], y + xy[1]}; + } + } +} diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java index 5e7f127fd4a6..c279c8f24362 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java @@ -1075,4 +1075,120 @@ public class InputMethodUtilsTest extends InstrumentationTestCase { assertTrue(subtypes2.isEmpty()); } } + + @SmallTest + public void testbuildInputMethodsAndSubtypesString() { + { + ArrayMap<String, ArraySet<String>> map = new ArrayMap<>(); + assertEquals("", InputMethodUtils.buildInputMethodsAndSubtypesString(map)); + } + { + ArrayMap<String, ArraySet<String>> map = new ArrayMap<>(); + map.put("ime0", new ArraySet<String>()); + assertEquals("ime0", InputMethodUtils.buildInputMethodsAndSubtypesString(map)); + } + { + ArrayMap<String, ArraySet<String>> map = new ArrayMap<>(); + ArraySet<String> subtypes1 = new ArraySet<>(); + subtypes1.add("subtype0"); + map.put("ime0", subtypes1); + assertEquals("ime0;subtype0", InputMethodUtils.buildInputMethodsAndSubtypesString(map)); + } + { + ArrayMap<String, ArraySet<String>> map = new ArrayMap<>(); + ArraySet<String> subtypes1 = new ArraySet<>(); + subtypes1.add("subtype0"); + subtypes1.add("subtype1"); + map.put("ime0", subtypes1); + + // We do not expect what order will be used to concatenate items in + // InputMethodUtils.buildInputMethodsAndSubtypesString() hence enumerate all possible + // permutations here. + ArraySet<String> validSequences = new ArraySet<>(); + validSequences.add("ime0;subtype0;subtype1"); + validSequences.add("ime0;subtype1;subtype0"); + assertTrue(validSequences.contains( + InputMethodUtils.buildInputMethodsAndSubtypesString(map))); + } + { + ArrayMap<String, ArraySet<String>> map = new ArrayMap<>(); + map.put("ime0", new ArraySet<String>()); + map.put("ime1", new ArraySet<String>()); + + ArraySet<String> validSequences = new ArraySet<>(); + validSequences.add("ime0:ime1"); + validSequences.add("ime1:ime0"); + assertTrue(validSequences.contains( + InputMethodUtils.buildInputMethodsAndSubtypesString(map))); + } + { + ArrayMap<String, ArraySet<String>> map = new ArrayMap<>(); + ArraySet<String> subtypes1 = new ArraySet<>(); + subtypes1.add("subtype0"); + map.put("ime0", subtypes1); + map.put("ime1", new ArraySet<String>()); + + ArraySet<String> validSequences = new ArraySet<>(); + validSequences.add("ime0;subtype0:ime1"); + validSequences.add("ime1;ime0;subtype0"); + assertTrue(validSequences.contains( + InputMethodUtils.buildInputMethodsAndSubtypesString(map))); + } + { + ArrayMap<String, ArraySet<String>> map = new ArrayMap<>(); + ArraySet<String> subtypes1 = new ArraySet<>(); + subtypes1.add("subtype0"); + subtypes1.add("subtype1"); + map.put("ime0", subtypes1); + map.put("ime1", new ArraySet<String>()); + + ArraySet<String> validSequences = new ArraySet<>(); + validSequences.add("ime0;subtype0;subtype1:ime1"); + validSequences.add("ime0;subtype1;subtype0:ime1"); + validSequences.add("ime1:ime0;subtype0;subtype1"); + validSequences.add("ime1:ime0;subtype1;subtype0"); + assertTrue(validSequences.contains( + InputMethodUtils.buildInputMethodsAndSubtypesString(map))); + } + { + ArrayMap<String, ArraySet<String>> map = new ArrayMap<>(); + ArraySet<String> subtypes1 = new ArraySet<>(); + subtypes1.add("subtype0"); + map.put("ime0", subtypes1); + + ArraySet<String> subtypes2 = new ArraySet<>(); + subtypes2.add("subtype1"); + map.put("ime1", subtypes2); + + ArraySet<String> validSequences = new ArraySet<>(); + validSequences.add("ime0;subtype0:ime1;subtype1"); + validSequences.add("ime1;subtype1:ime0;subtype0"); + assertTrue(validSequences.contains( + InputMethodUtils.buildInputMethodsAndSubtypesString(map))); + } + { + ArrayMap<String, ArraySet<String>> map = new ArrayMap<>(); + ArraySet<String> subtypes1 = new ArraySet<>(); + subtypes1.add("subtype0"); + subtypes1.add("subtype1"); + map.put("ime0", subtypes1); + + ArraySet<String> subtypes2 = new ArraySet<>(); + subtypes2.add("subtype2"); + subtypes2.add("subtype3"); + map.put("ime1", subtypes2); + + ArraySet<String> validSequences = new ArraySet<>(); + validSequences.add("ime0;subtype0;subtype1:ime1;subtype2;subtype3"); + validSequences.add("ime0;subtype1;subtype0:ime1;subtype2;subtype3"); + validSequences.add("ime0;subtype0;subtype1:ime1;subtype3;subtype2"); + validSequences.add("ime0;subtype1;subtype0:ime1;subtype3;subtype2"); + validSequences.add("ime1;subtype2;subtype3:ime0;subtype0;subtype1"); + validSequences.add("ime2;subtype3;subtype2:ime0;subtype0;subtype1"); + validSequences.add("ime3;subtype2;subtype3:ime0;subtype1;subtype0"); + validSequences.add("ime4;subtype3;subtype2:ime0;subtype1;subtype0"); + assertTrue(validSequences.contains( + InputMethodUtils.buildInputMethodsAndSubtypesString(map))); + } + } } diff --git a/docs/html/guide/topics/renderscript/reference/rs_for_each.jd b/docs/html/guide/topics/renderscript/reference/rs_for_each.jd index abbbda78ba3a..9ba56149f142 100644 --- a/docs/html/guide/topics/renderscript/reference/rs_for_each.jd +++ b/docs/html/guide/topics/renderscript/reference/rs_for_each.jd @@ -205,7 +205,7 @@ locality when the processing is distributed over multiple cores. <span class='normal'>: Handle to a kernel invocation context</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: const struct rs_kernel_context_t * Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> +<p>A typedef of: const struct rs_kernel_context_t * Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> </p> <p> The kernel context contains common characteristics of the allocations being iterated over, like dimensions. It also contains rarely used indices of the currently processed diff --git a/docs/html/guide/topics/renderscript/reference/rs_graphics.jd b/docs/html/guide/topics/renderscript/reference/rs_graphics.jd index 1b115fe9a793..b04451c7a526 100644 --- a/docs/html/guide/topics/renderscript/reference/rs_graphics.jd +++ b/docs/html/guide/topics/renderscript/reference/rs_graphics.jd @@ -502,7 +502,7 @@ page.title=RenderScript Graphics Functions and Types </h4> <div class='jd-details-descr'> <p>An enum with the following values: - When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> +When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_BLEND_DST_ZERO = 0</th><td></td></tr> @@ -527,7 +527,7 @@ page.title=RenderScript Graphics Functions and Types </h4> <div class='jd-details-descr'> <p>An enum with the following values: - When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> +When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_BLEND_SRC_ZERO = 0</th><td></td></tr> @@ -553,7 +553,7 @@ page.title=RenderScript Graphics Functions and Types </h4> <div class='jd-details-descr'> <p>An enum with the following values: - When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> +When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_CULL_BACK = 0</th><td></td></tr> @@ -573,7 +573,7 @@ page.title=RenderScript Graphics Functions and Types </h4> <div class='jd-details-descr'> <p>An enum with the following values: - When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> +When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_DEPTH_FUNC_ALWAYS = 0</th><td>Always drawn</td></tr> @@ -599,11 +599,7 @@ depth to that found in the depth buffer. <span class='normal'>: Handle to a Font</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE __attribute__(( -#if (defined(RS_VERSION) && (RS_VERSION >= 1)) -deprecated -#endif -)) When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> +<p>When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> </p> <p><b>Deprecated.</b> Do not use.</p> <p> Opaque handle to a RenderScript font object. @@ -619,11 +615,7 @@ See: android.renderscript.Font <span class='normal'>: Handle to a Mesh</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE __attribute__(( -#if (defined(RS_VERSION) && (RS_VERSION >= 1)) -deprecated -#endif -)) When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> +<p>When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> </p> <p><b>Deprecated.</b> Do not use.</p> <p> Opaque handle to a RenderScript mesh object. @@ -640,7 +632,7 @@ See: android.renderscript.Mesh </h4> <div class='jd-details-descr'> <p>An enum with the following values: - When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> +When compiling for 32 bits. <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16 - 22</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_PRIMITIVE_POINT = 0</th><td>Vertex data will be rendered as a series of points</td></tr> @@ -664,11 +656,7 @@ See: android.renderscript.Mesh <span class='normal'>: Handle to a ProgramFragment</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE __attribute__(( -#if (defined(RS_VERSION) && (RS_VERSION >= 1)) -deprecated -#endif -)) When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> +<p>When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> </p> <p><b>Deprecated.</b> Do not use.</p> <p> Opaque handle to a RenderScript ProgramFragment object. @@ -684,11 +672,7 @@ See: android.renderscript.ProgramFragment <span class='normal'>: Handle to a ProgramRaster</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE __attribute__(( -#if (defined(RS_VERSION) && (RS_VERSION >= 1)) -deprecated -#endif -)) When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> +<p>When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> </p> <p><b>Deprecated.</b> Do not use.</p> <p> Opaque handle to a RenderScript ProgramRaster object. @@ -704,11 +688,7 @@ See: android.renderscript.ProgramRaster <span class='normal'>: Handle to a ProgramStore</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE __attribute__(( -#if (defined(RS_VERSION) && (RS_VERSION >= 1)) -deprecated -#endif -)) When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> +<p>When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> </p> <p><b>Deprecated.</b> Do not use.</p> <p> Opaque handle to a RenderScript ProgramStore object. @@ -724,11 +704,7 @@ See: android.renderscript.ProgramStore <span class='normal'>: Handle to a ProgramVertex</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE __attribute__(( -#if (defined(RS_VERSION) && (RS_VERSION >= 1)) -deprecated -#endif -)) When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> +<p>When compiling for 32 bits. Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23 and higher</a> </p> <p><b>Deprecated.</b> Do not use.</p> <p> Opaque handle to a RenderScript ProgramVertex object. diff --git a/docs/html/guide/topics/renderscript/reference/rs_object_types.jd b/docs/html/guide/topics/renderscript/reference/rs_object_types.jd index f3428966c7de..ac4745482d1f 100644 --- a/docs/html/guide/topics/renderscript/reference/rs_object_types.jd +++ b/docs/html/guide/topics/renderscript/reference/rs_object_types.jd @@ -99,7 +99,7 @@ elements, and scripts. Most of these object are created using the Java RenderSc <span class='normal'>: Handle to an allocation</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE </p> +<p></p> <p> An opaque handle to a RenderScript allocation. </p> @@ -116,7 +116,7 @@ elements, and scripts. Most of these object are created using the Java RenderSc </h4> <div class='jd-details-descr'> <p>An enum with the following values: - Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 14</a> +Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 14</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X = 0</th><td></td></tr> @@ -139,7 +139,7 @@ elements, and scripts. Most of these object are created using the Java RenderSc </h4> <div class='jd-details-descr'> <p>An enum with the following values: - Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 14</a> +Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 14</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_ALLOCATION_USAGE_SCRIPT = 0x0001</th><td>Allocation is bound to and accessed by scripts.</td></tr> @@ -165,7 +165,7 @@ relevant to an allocation or an operation on an allocation. </h4> <div class='jd-details-descr'> <p>An enum with the following values: - Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16</a> +Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_KIND_USER = 0</th><td>No special interpretation.</td></tr> @@ -202,7 +202,7 @@ texture formats. </h4> <div class='jd-details-descr'> <p>An enum with the following values: - Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16</a> +Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_TYPE_NONE = 0</th><td>Element is a complex type, i.e. a struct.</td></tr> @@ -253,7 +253,7 @@ as a single unit for packing and alignment purposes. <span class='normal'>: Handle to an element</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE </p> +<p></p> <p> An opaque handle to a RenderScript element. </p> @@ -269,7 +269,7 @@ as a single unit for packing and alignment purposes. <span class='normal'>: Handle to a Sampler</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE </p> +<p></p> <p> An opaque handle to a RenderScript sampler object. </p> @@ -286,7 +286,7 @@ as a single unit for packing and alignment purposes. </h4> <div class='jd-details-descr'> <p>An enum with the following values: - Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16</a> +Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 16</a> </p> <table class='jd-tagtable'><tbody> <tr><th>RS_SAMPLER_NEAREST = 0</th><td></td></tr> @@ -308,7 +308,7 @@ as a single unit for packing and alignment purposes. <span class='normal'>: Handle to a Script</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE </p> +<p></p> <p> An opaque handle to a RenderScript script object. </p> @@ -324,7 +324,7 @@ as a single unit for packing and alignment purposes. <span class='normal'>: Handle to a Type</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: _RS_HANDLE </p> +<p></p> <p> An opaque handle to a RenderScript type. </p> diff --git a/docs/html/guide/topics/renderscript/reference/rs_time.jd b/docs/html/guide/topics/renderscript/reference/rs_time.jd index 27044a3d3126..a1cedfb954db 100644 --- a/docs/html/guide/topics/renderscript/reference/rs_time.jd +++ b/docs/html/guide/topics/renderscript/reference/rs_time.jd @@ -78,9 +78,9 @@ system up time. It is not recommended to call these functions inside of a kerne <span class='normal'>: Seconds since January 1, 1970</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: int When compiling for 32 bits. +<p>A typedef of: int When compiling for 32 bits. </p> -<p>A typedef of: long When compiling for 64 bits. +<p>A typedef of: long When compiling for 64 bits. </p> <p> Calendar time interpreted as seconds elapsed since the Epoch (00:00:00 on January 1, 1970, Coordinated Universal Time (UTC)). diff --git a/docs/html/guide/topics/renderscript/reference/rs_value_types.jd b/docs/html/guide/topics/renderscript/reference/rs_value_types.jd index d53caf92d74a..2bd49dc5cf7c 100644 --- a/docs/html/guide/topics/renderscript/reference/rs_value_types.jd +++ b/docs/html/guide/topics/renderscript/reference/rs_value_types.jd @@ -644,7 +644,7 @@ with a 128 bit alignment. <span class='normal'>: 16 bit floating point value</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: __fp16 Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> +<p>A typedef of: __fp16 Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> </p> <p> A 16 bit floating point value. </p> @@ -658,7 +658,7 @@ with a 128 bit alignment. <span class='normal'>: Two 16 bit floats</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: half __attribute__((ext_vector_type(2))) Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> +<p>A typedef of: half __attribute__((ext_vector_type(2))) Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> </p> <p> Vector version of the half float type. Provides two half fields packed into a single 32 bit field with 32 bit alignment. @@ -673,7 +673,7 @@ into a single 32 bit field with 32 bit alignment. <span class='normal'>: Three 16 bit floats</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: half __attribute__((ext_vector_type(3))) Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> +<p>A typedef of: half __attribute__((ext_vector_type(3))) Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> </p> <p> Vector version of the half float type. Provides three half fields packed into a single 64 bit field with 64 bit alignment. @@ -688,7 +688,7 @@ into a single 64 bit field with 64 bit alignment. <span class='normal'>: Four 16 bit floats</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: half __attribute__((ext_vector_type(4))) Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> +<p>A typedef of: half __attribute__((ext_vector_type(4))) Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 23</a> </p> <p> Vector version of the half float type. Provides four half fields packed into a single 64 bit field with 64 bit alignment. @@ -771,9 +771,9 @@ with a 128 bit alignment. <span class='normal'>: 64 bit signed integer</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: long long Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 21 and higher</a> +<p>A typedef of: long long Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 21 and higher</a> </p> -<p>A typedef of: long Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 21</a> +<p>A typedef of: long Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 21</a> </p> <p> A 64 bit signed integer type. </p> @@ -960,9 +960,9 @@ with a 64 bit alignment. <span class='normal'>: Unsigned size type</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: uint64_t When compiling for 64 bits. +<p>A typedef of: uint64_t When compiling for 64 bits. </p> -<p>A typedef of: uint32_t When compiling for 32 bits. +<p>A typedef of: uint32_t When compiling for 32 bits. </p> <p> Unsigned size type. The number of bits depend on the compilation flags. </p> @@ -976,9 +976,9 @@ with a 64 bit alignment. <span class='normal'>: Signed size type</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: int64_t When compiling for 64 bits. +<p>A typedef of: int64_t When compiling for 64 bits. </p> -<p>A typedef of: int32_t When compiling for 32 bits. +<p>A typedef of: int32_t When compiling for 32 bits. </p> <p> Signed size type. The number of bits depend on the compilation flags. </p> @@ -1128,9 +1128,9 @@ with a 128 bit alignment. <span class='normal'>: 64 bit unsigned integer</span> </h4> <div class='jd-details-descr'> -<p>A typedef of: unsigned long long Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 21 and higher</a> +<p>A typedef of: unsigned long long Removed from <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 21 and higher</a> </p> -<p>A typedef of: unsigned long Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 21</a> +<p>A typedef of: unsigned long Added in <a href='http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels'>API level 21</a> </p> <p> A 64 bit unsigned integer type. </p> diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp index a705bcc37406..090be88561c4 100644 --- a/media/jni/soundpool/SoundPool.cpp +++ b/media/jni/soundpool/SoundPool.cpp @@ -589,6 +589,9 @@ static status_t decode(int fd, int64_t offset, int64_t length, ALOGV("format changed to: %s", AMediaFormat_toString(format)); } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) { ALOGV("no output buffer right now"); + } else if (status <= AMEDIA_ERROR_BASE) { + ALOGE("decode error: %d", status); + break; } else { ALOGV("unexpected info code: %d", status); } diff --git a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml index 6959c65e2d3a..6dcbb38cb950 100644 --- a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml +++ b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml @@ -17,7 +17,8 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_activated="true" - android:color="?android:attr/colorControlHighlight" /> + android:color="?android:attr/colorAccent" + android:alpha="0.1" /> <item android:state_enabled="false" android:color="?android:attr/colorBackground" diff --git a/packages/DocumentsUI/res/values/attrs.xml b/packages/DocumentsUI/res/values/attrs.xml new file mode 100644 index 000000000000..0afc3a2e4d8b --- /dev/null +++ b/packages/DocumentsUI/res/values/attrs.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <declare-styleable name="DocumentsBaseTheme"> + <attr name="colorActionMode" format="color"/> + </declare-styleable> +</resources> diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml index 6002dde5040a..cb6957dd3463 100644 --- a/packages/DocumentsUI/res/values/colors.xml +++ b/packages/DocumentsUI/res/values/colors.xml @@ -19,18 +19,20 @@ <color name="material_grey_300">#ffeeeeee</color> <color name="material_grey_600">#ff757575</color> <color name="material_grey_800">#ff424242</color> - <color name="material_blue_700">#ff1976d2</color> - <color name="material_blue_500">#ff2196f3</color> <color name="primary_dark">@*android:color/material_blue_grey_900</color> <color name="primary">@*android:color/material_blue_grey_800</color> <color name="accent">@*android:color/material_deep_teal_500</color> + <color name="platform_blue_100">#ffd0d9ff</color> + <color name="platform_blue_500">#ff5677fc</color> + <color name="platform_blue_700">#ff455ede</color> + <color name="platform_blue_a100">#ffa6baff</color> + <color name="platform_blue_a200">#ffff5177</color> + <color name="directory_background">@color/material_grey_300</color> <color name="item_doc_grid_background">#FFFFFFFF</color> <color name="item_doc_grid_protect_background">#88000000</color> - <color name="status_bar_background">@color/material_blue_700</color> - <color name="action_mode_status_bar_background">@color/material_grey_800</color> <color name="band_select_background">#88ffffff</color> <color name="band_select_border">#44000000</color> </resources> diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml index e67f956112ce..8301816edaea 100644 --- a/packages/DocumentsUI/res/values/styles.xml +++ b/packages/DocumentsUI/res/values/styles.xml @@ -28,6 +28,7 @@ <item name="android:colorPrimaryDark">@color/primary_dark</item> <item name="android:colorPrimary">@color/primary</item> <item name="android:colorAccent">@color/accent</item> + <item name="colorActionMode">@color/material_grey_800</item> <item name="android:listDivider">@*android:drawable/list_divider_material</item> @@ -43,6 +44,7 @@ <item name="actionBarWidgetTheme">@null</item> <item name="actionBarTheme">@style/ActionBarTheme</item> <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item> + <item name="colorActionMode">@color/material_grey_800</item> <item name="android:listDivider">@*android:drawable/list_divider_material</item> @@ -68,16 +70,21 @@ </style> <style name="AlertDialogTheme" parent="@android:style/Theme.Material.Light.Dialog.Alert"> - <item name="android:colorAccent">@color/material_blue_700</item> + <item name="android:colorAccent">@color/platform_blue_700</item> </style> <style name="FilesTheme" parent="@style/DocumentsBaseTheme.FullScreen"> - <item name="android:colorPrimaryDark">@color/material_blue_700</item> - <item name="android:colorPrimary">@color/material_blue_500</item> - <item name="android:colorAccent">@color/material_blue_700</item> - <item name="android:actionModeStyle">@style/ActionModeStyle</item> + <item name="android:colorPrimaryDark">@color/platform_blue_700</item> + <item name="android:colorPrimary">@color/platform_blue_500</item> + <item name="android:colorAccent">@color/platform_blue_700</item> + <item name="android:actionModeStyle">@style/FilesActionModeStyle</item> + <item name="colorActionMode">@color/platform_blue_700</item> <item name="android:alertDialogTheme">@style/AlertDialogTheme</item> </style> + <style name="FilesActionModeStyle" parent="@android:style/Widget.Material.Light.ActionMode"> + <item name="android:background">@color/platform_blue_100</item> + </style> + </resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index cfd4ecb96b59..046f3df3b5d4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -637,27 +637,25 @@ public class DirectoryFragment extends Fragment { @Override public void onSelectionChanged() { mModel.getSelection(mSelected); + TypedValue color = new TypedValue(); if (mSelected.size() > 0) { if (DEBUG) Log.d(TAG, "Maybe starting action mode."); if (mActionMode == null) { if (DEBUG) Log.d(TAG, "Yeah. Starting action mode."); mActionMode = getActivity().startActionMode(this); - getActivity().getWindow().setStatusBarColor( - getResources().getColor(R.color.action_mode_status_bar_background)); } + getActivity().getTheme().resolveAttribute( + R.attr.colorActionMode, color, true); updateActionMenu(); } else { if (DEBUG) Log.d(TAG, "Finishing action mode."); if (mActionMode != null) { mActionMode.finish(); } - // Obtain the original status bar color from the theme, and restore it. - TypedValue color = new TypedValue(); getActivity().getTheme().resolveAttribute( android.R.attr.colorPrimaryDark, color, true); - getActivity().getWindow().setStatusBarColor(color.data); - } + getActivity().getWindow().setStatusBarColor(color.data); if (mActionMode != null) { mActionMode.setTitle(TextUtils.formatSelectedCount(mSelected.size())); diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java index 8f9025a1cdeb..450def799a1a 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java @@ -90,10 +90,6 @@ public class FilesActivity extends BaseActivity { mClipper = new DocumentClipper(this); mDrawer = DrawerController.create(this); - if (mDrawer.isPresent()) { - setTheme(R.style.DocumentsNonDialogTheme); - } - RootsFragment.show(getFragmentManager(), null); if (!mState.restored) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java index d4c4331267a0..0d4265a6417c 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java @@ -91,12 +91,12 @@ class DocumentLoader { return task.createCursor(mResolver, columnNames); } - synchronized void clearTasks(int deviceId) { - mTaskList.clearTaskForDevice(deviceId); + synchronized void clearTasks() { + mTaskList.clear(); } synchronized void clearCompletedTasks() { - mTaskList.clearCompletedTask(); + mTaskList.clearCompletedTasks(); } synchronized void clearTask(Identifier parentIdentifier) { @@ -162,18 +162,7 @@ class DocumentLoader { return null; } - void clearTaskForDevice(int deviceId) { - int i = 0; - while (i < size()) { - if (get(i).mIdentifier.mDeviceId == deviceId) { - remove(i); - } else { - i++; - } - } - } - - void clearCompletedTask() { + void clearCompletedTasks() { int i = 0; while (i < size()) { if (get(i).completed()) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index 78ed161d47f4..a1a43c18a72f 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -34,6 +34,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; /** * DocumentsProvider for MTP devices. @@ -56,8 +58,8 @@ public class MtpDocumentsProvider extends DocumentsProvider { private MtpManager mMtpManager; private ContentResolver mResolver; - private PipeManager mPipeManager; - private DocumentLoader mDocumentLoader; + private Map<Integer, DeviceToolkit> mDeviceToolkits; + private DocumentLoader mDocumentLoaders; private RootScanner mRootScanner; /** @@ -72,8 +74,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { sSingleton = this; mMtpManager = new MtpManager(getContext()); mResolver = getContext().getContentResolver(); - mPipeManager = new PipeManager(); - mDocumentLoader = new DocumentLoader(mMtpManager, mResolver); + mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); mRootScanner = new RootScanner(mResolver, mMtpManager); return true; } @@ -82,7 +83,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { void onCreateForTesting(MtpManager mtpManager, ContentResolver resolver) { mMtpManager = mtpManager; mResolver = resolver; - mDocumentLoader = new DocumentLoader(mMtpManager, mResolver); + mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); mRootScanner = new RootScanner(mResolver, mMtpManager); } @@ -158,7 +159,8 @@ public class MtpDocumentsProvider extends DocumentsProvider { } final Identifier parentIdentifier = Identifier.createFromDocumentId(parentDocumentId); try { - return mDocumentLoader.queryChildDocuments(projection, parentIdentifier); + return getDocumentLoader(parentIdentifier).queryChildDocuments( + projection, parentIdentifier); } catch (IOException exception) { throw new FileNotFoundException(exception.getMessage()); } @@ -172,11 +174,12 @@ public class MtpDocumentsProvider extends DocumentsProvider { try { switch (mode) { case "r": - return mPipeManager.readDocument(mMtpManager, identifier); + return getPipeManager(identifier).readDocument(mMtpManager, identifier); case "w": // TODO: Clear the parent document loader task (if exists) and call notify // when writing is completed. - return mPipeManager.writeDocument(getContext(), mMtpManager, identifier); + return getPipeManager(identifier).writeDocument( + getContext(), mMtpManager, identifier); default: // TODO: Add support for seekable files. throw new UnsupportedOperationException( @@ -195,7 +198,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { final Identifier identifier = Identifier.createFromDocumentId(documentId); try { return new AssetFileDescriptor( - mPipeManager.readThumbnail(mMtpManager, identifier), + getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 0, // Start offset. AssetFileDescriptor.UNKNOWN_LENGTH); } catch (IOException error) { @@ -212,7 +215,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); final Identifier parentIdentifier = new Identifier( identifier.mDeviceId, identifier.mStorageId, parentHandle); - mDocumentLoader.clearTask(parentIdentifier); + getDocumentLoader(parentIdentifier).clearTask(parentIdentifier); notifyChildDocumentsChange(parentIdentifier.toDocumentId()); } catch (IOException error) { throw new FileNotFoundException(error.getMessage()); @@ -221,7 +224,9 @@ public class MtpDocumentsProvider extends DocumentsProvider { @Override public void onTrimMemory(int level) { - mDocumentLoader.clearCompletedTasks(); + for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { + toolkit.mDocumentLoader.clearCompletedTasks(); + } } @Override @@ -241,7 +246,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { .build(), pipe[1]); final String documentId = new Identifier(parentId.mDeviceId, parentId.mStorageId, objectHandle).toDocumentId(); - mDocumentLoader.clearTask(parentId); + getDocumentLoader(parentId).clearTask(parentId); notifyChildDocumentsChange(parentDocumentId); return documentId; } catch (IOException error) { @@ -252,12 +257,15 @@ public class MtpDocumentsProvider extends DocumentsProvider { void openDevice(int deviceId) throws IOException { mMtpManager.openDevice(deviceId); + mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver)); mRootScanner.scanNow(); } void closeDevice(int deviceId) throws IOException { + // TODO: Flush the device before closing (if not closed externally). + getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); + mDeviceToolkits.remove(deviceId); mMtpManager.closeDevice(deviceId); - mDocumentLoader.clearTasks(deviceId); mRootScanner.scanNow(); } @@ -266,7 +274,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { for (int deviceId : mMtpManager.getOpenedDeviceIds()) { try { mMtpManager.closeDevice(deviceId); - mDocumentLoader.clearTasks(deviceId); + getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); closed = true; } catch (IOException d) { Log.d(TAG, "Failed to close the MTP device: " + deviceId); @@ -287,4 +295,30 @@ public class MtpDocumentsProvider extends DocumentsProvider { null, false); } + + private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { + final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); + if (toolkit == null) { + throw new FileNotFoundException(); + } + return toolkit; + } + + private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { + return getDeviceToolkit(identifier.mDeviceId).mPipeManager; + } + + private DocumentLoader getDocumentLoader(Identifier identifier) throws FileNotFoundException { + return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; + } + + private static class DeviceToolkit { + public final PipeManager mPipeManager; + public final DocumentLoader mDocumentLoader; + + public DeviceToolkit(MtpManager manager, ContentResolver resolver) { + mPipeManager = new PipeManager(); + mDocumentLoader = new DocumentLoader(manager, resolver); + } + } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java index ca78a3e1b775..7cc7413bab3d 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java @@ -42,7 +42,7 @@ class MtpManager { private final SparseArray<MtpDevice> mDevices = new SparseArray<>(); MtpManager(Context context) { - mManager = (UsbManager)context.getSystemService(Context.USB_SERVICE); + mManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); } synchronized void openDevice(int deviceId) throws IOException { @@ -96,78 +96,96 @@ class MtpManager { return result; } - synchronized MtpRoot[] getRoots(int deviceId) throws IOException { + MtpRoot[] getRoots(int deviceId) throws IOException { final MtpDevice device = getDevice(deviceId); - final int[] storageIds = device.getStorageIds(); - if (storageIds == null) { - throw new IOException("Failed to obtain storage IDs."); - } - final MtpRoot[] results = new MtpRoot[storageIds.length]; - for (int i = 0; i < storageIds.length; i++) { - results[i] = new MtpRoot(deviceId, device.getStorageInfo(storageIds[i])); + synchronized (device) { + final int[] storageIds = device.getStorageIds(); + if (storageIds == null) { + throw new IOException("Failed to obtain storage IDs."); + } + final MtpRoot[] results = new MtpRoot[storageIds.length]; + for (int i = 0; i < storageIds.length; i++) { + results[i] = new MtpRoot(deviceId, device.getStorageInfo(storageIds[i])); + } + return results; } - return results; } - synchronized MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) + MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); - return device.getObjectInfo(objectHandle); + synchronized (device) { + return device.getObjectInfo(objectHandle); + } } - synchronized int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle) + int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); - return device.getObjectHandles(storageId, 0 /* all format */, parentObjectHandle); + synchronized (device) { + return device.getObjectHandles(storageId, 0 /* all format */, parentObjectHandle); + } } - synchronized byte[] getObject(int deviceId, int objectHandle, int expectedSize) + byte[] getObject(int deviceId, int objectHandle, int expectedSize) throws IOException { final MtpDevice device = getDevice(deviceId); - return device.getObject(objectHandle, expectedSize); + synchronized (device) { + return device.getObject(objectHandle, expectedSize); + } } - synchronized byte[] getThumbnail(int deviceId, int objectHandle) throws IOException { + byte[] getThumbnail(int deviceId, int objectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); - return device.getThumbnail(objectHandle); + synchronized (device) { + return device.getThumbnail(objectHandle); + } } - synchronized void deleteDocument(int deviceId, int objectHandle) throws IOException { + void deleteDocument(int deviceId, int objectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); - if (!device.deleteObject(objectHandle)) { - throw new IOException("Failed to delete document"); + synchronized (device) { + if (!device.deleteObject(objectHandle)) { + throw new IOException("Failed to delete document"); + } } } - synchronized int createDocument(int deviceId, MtpObjectInfo objectInfo, + int createDocument(int deviceId, MtpObjectInfo objectInfo, ParcelFileDescriptor source) throws IOException { final MtpDevice device = getDevice(deviceId); - final MtpObjectInfo sendObjectInfoResult = device.sendObjectInfo(objectInfo); - if (sendObjectInfoResult == null) { - throw new IOException("Failed to create a document"); - } - if (objectInfo.getFormat() != MtpConstants.FORMAT_ASSOCIATION) { - if (!device.sendObject(sendObjectInfoResult.getObjectHandle(), - sendObjectInfoResult.getCompressedSize(), source)) { - throw new IOException("Failed to send contents of a document"); + synchronized (device) { + final MtpObjectInfo sendObjectInfoResult = device.sendObjectInfo(objectInfo); + if (sendObjectInfoResult == null) { + throw new IOException("Failed to create a document"); + } + if (objectInfo.getFormat() != MtpConstants.FORMAT_ASSOCIATION) { + if (!device.sendObject(sendObjectInfoResult.getObjectHandle(), + sendObjectInfoResult.getCompressedSize(), source)) { + throw new IOException("Failed to send contents of a document"); + } } + return sendObjectInfoResult.getObjectHandle(); } - return sendObjectInfoResult.getObjectHandle(); } - synchronized int getParent(int deviceId, int objectHandle) throws IOException { + int getParent(int deviceId, int objectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); - final int result = (int) device.getParent(objectHandle); - if (result < 0) { - throw new FileNotFoundException("Not found parent object"); + synchronized (device) { + final int result = (int) device.getParent(objectHandle); + if (result < 0) { + throw new FileNotFoundException("Not found parent object"); + } + return result; } - return result; } - synchronized void importFile(int deviceId, int objectHandle, ParcelFileDescriptor target) + void importFile(int deviceId, int objectHandle, ParcelFileDescriptor target) throws IOException { final MtpDevice device = getDevice(deviceId); - device.importFile(objectHandle, target); + synchronized (device) { + device.importFile(objectHandle, target); + } } private MtpDevice getDevice(int deviceId) throws IOException { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java index cd38f1e852b5..affaebd05c16 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java @@ -33,7 +33,7 @@ class PipeManager { final ExecutorService mExecutor; PipeManager() { - this(Executors.newCachedThreadPool()); + this(Executors.newSingleThreadExecutor()); } PipeManager(ExecutorService executor) { diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 9b316be48a15..4b3a5cd061bf 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -39,7 +39,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { private TestMtpManager mMtpManager; @Override - public void setUp() { + public void setUp() throws IOException { mResolver = new TestContentResolver(); mMtpManager = new TestMtpManager(getContext()); mProvider = new MtpDocumentsProvider(); @@ -207,6 +207,8 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } public void testQueryDocument() throws IOException { + mMtpManager.addValidDevice(0); + mProvider.openDevice(0); mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder() .setObjectHandle(2) .setFormat(MtpConstants.FORMAT_EXIF_JPEG) @@ -232,6 +234,8 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } public void testQueryDocument_directory() throws IOException { + mMtpManager.addValidDevice(0); + mProvider.openDevice(0); mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder() .setObjectHandle(2) .setFormat(MtpConstants.FORMAT_ASSOCIATION) @@ -255,6 +259,8 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } public void testQueryDocument_forRoot() throws IOException { + mMtpManager.addValidDevice(0); + mProvider.openDevice(0); mMtpManager.setRoots(0, new MtpRoot[] { new MtpRoot( 0 /* deviceId */, @@ -277,6 +283,8 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } public void testQueryChildDocuments() throws Exception { + mMtpManager.addValidDevice(0); + mProvider.openDevice(0); mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder() @@ -303,6 +311,8 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } public void testQueryChildDocuments_cursorError() throws Exception { + mMtpManager.addValidDevice(0); + mProvider.openDevice(0); try { mProvider.queryChildDocuments("0_0_0", null, null); fail(); @@ -312,6 +322,8 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } public void testQueryChildDocuments_documentError() throws Exception { + mMtpManager.addValidDevice(0); + mProvider.openDevice(0); mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); try { mProvider.queryChildDocuments("0_0_0", null, null); @@ -321,7 +333,9 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } } - public void testDeleteDocument() throws FileNotFoundException { + public void testDeleteDocument() throws IOException { + mMtpManager.addValidDevice(0); + mProvider.openDevice(0); mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder() .setObjectHandle(1) .setParent(2) @@ -332,7 +346,9 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { MtpDocumentsProvider.AUTHORITY, "0_0_2"))); } - public void testDeleteDocument_error() { + public void testDeleteDocument_error() throws IOException { + mMtpManager.addValidDevice(0); + mProvider.openDevice(0); mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder() .setObjectHandle(2) .build()); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 3514c5de0d09..87dfab93dd55 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -155,6 +155,7 @@ public class KeyguardViewMediator extends SystemUI { private static final int NOTIFY_STARTED_WAKING_UP = 21; private static final int NOTIFY_SCREEN_TURNED_ON = 22; private static final int NOTIFY_SCREEN_TURNED_OFF = 23; + private static final int NOTIFY_STARTED_GOING_TO_SLEEP = 24; /** * The default amount of time we stay awake (used for all key input) @@ -677,6 +678,7 @@ public class KeyguardViewMediator extends SystemUI { playSounds(true); } } + notifyStartedGoingToSleep(); } public void onFinishedGoingToSleep(int why) { @@ -1098,6 +1100,11 @@ public class KeyguardViewMediator extends SystemUI { mHandler.sendEmptyMessage(VERIFY_UNLOCK); } + private void notifyStartedGoingToSleep() { + if (DEBUG) Log.d(TAG, "notifyStartedGoingToSleep"); + mHandler.sendEmptyMessage(NOTIFY_STARTED_GOING_TO_SLEEP); + } + private void notifyFinishedGoingToSleep() { if (DEBUG) Log.d(TAG, "notifyFinishedGoingToSleep"); mHandler.sendEmptyMessage(NOTIFY_FINISHED_GOING_TO_SLEEP); @@ -1209,6 +1216,9 @@ public class KeyguardViewMediator extends SystemUI { case VERIFY_UNLOCK: handleVerifyUnlock(); break; + case NOTIFY_STARTED_GOING_TO_SLEEP: + handleNotifyStartedGoingToSleep(); + break; case NOTIFY_FINISHED_GOING_TO_SLEEP: handleNotifyFinishedGoingToSleep(); break; @@ -1546,6 +1556,13 @@ public class KeyguardViewMediator extends SystemUI { } } + private void handleNotifyStartedGoingToSleep() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleNotifyStartedGoingToSleep"); + mStatusBarKeyguardViewManager.onStartedGoingToSleep(); + } + } + /** * Handle message sent by {@link #notifyFinishedGoingToSleep()} * @see #NOTIFY_FINISHED_GOING_TO_SLEEP diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java index 21ab7e582762..28f111fce4eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java @@ -69,7 +69,8 @@ import java.util.List; * Navigation bar contains both pinned and unpinned apps: pinned in the left part, unpinned in the * right part, with no separator in between. */ -class NavigationBarApps extends LinearLayout { +class NavigationBarApps extends LinearLayout + implements NavigationBarAppsModel.OnAppsChangedListener { public final static boolean DEBUG = false; private final static String TAG = "NavigationBarApps"; @@ -120,11 +121,20 @@ class NavigationBarApps extends LinearLayout { // has a child that will be moved to make the menu to appear where we need it. private final ViewGroup mPopupAnchor; private final PopupMenu mPopupMenu; + + /** + * True if popup menu code is busy with a popup operation. + * Attempting to show a popup menu or to add menu items while it's returning true will + * corrupt/crash the app. + */ + private boolean mIsPopupInUse = false; private final int [] mClickedIconLocation = new int[2]; public NavigationBarApps(Context context, AttributeSet attrs) { super(context, attrs); - sAppsModel = new NavigationBarAppsModel(context); + if (sAppsModel == null) { + sAppsModel = new NavigationBarAppsModel(context); + } mPackageManager = context.getPackageManager(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); @@ -278,6 +288,7 @@ class NavigationBarApps extends LinearLayout { mContext.registerReceiver(mBroadcastReceiver, filter); mAppPackageMonitor.register(mContext, null, UserHandle.ALL, true); + sAppsModel.addOnAppsChangedListener(this); } @Override @@ -285,6 +296,16 @@ class NavigationBarApps extends LinearLayout { super.onDetachedFromWindow(); mContext.unregisterReceiver(mBroadcastReceiver); mAppPackageMonitor.unregister(); + sAppsModel.removeOnAppsChangedListener(this); + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + if (mIsPopupInUse && !isShown()) { + // Hide the popup if current view became invisible. + shutdownPopupMenu(); + } } private void addAppButton(AppButtonData appButtonData) { @@ -299,6 +320,19 @@ class NavigationBarApps extends LinearLayout { new GetActivityIconTask(mPackageManager, button).execute(appButtonData); } + private List<AppInfo> getPinnedApps() { + List<AppInfo> apps = new ArrayList<AppInfo>(); + int childCount = getChildCount(); + for (int i = 0; i != childCount; ++i) { + View child = getChildAt(i); + AppButtonData appButtonData = (AppButtonData)child.getTag(); + if (appButtonData == null) continue; // Skip the drag placeholder. + if(!appButtonData.pinned) continue; + apps.add(appButtonData.appInfo); + } + return apps; + } + /** * Creates an ImageView icon for each pinned app. Removes any existing icons. May be called * to synchronize the current view with the shared data mode. @@ -319,16 +353,7 @@ class NavigationBarApps extends LinearLayout { * Saves pinned apps stored in app icons into the data model. */ private void savePinnedApps() { - List<AppInfo> apps = new ArrayList<AppInfo>(); - int childCount = getChildCount(); - for (int i = 0; i != childCount; ++i) { - View child = getChildAt(i); - AppButtonData appButtonData = (AppButtonData)child.getTag(); - if (appButtonData == null) continue; // Skip the drag placeholder. - if(!appButtonData.pinned) continue; - apps.add(appButtonData.appInfo); - } - sAppsModel.setApps(apps); + sAppsModel.setApps(getPinnedApps()); } /** @@ -671,14 +696,12 @@ class NavigationBarApps extends LinearLayout { } /** - * Returns true if popup menu code is busy with a popup operation. - * Attempting to show a popup menu or to add menu items while it's returning true will - * corrupt/crash the app. + * Brings the menu popup to closed state. + * Can be called at any stage of the asynchronous process of showing a menu. */ - boolean isPopupInUse() { - // mPopupAnchor's parent will be set to non-null/null by mWindowManager.add/RemoveView - // correspondingly. - return mPopupAnchor.getParent() != null; + private void shutdownPopupMenu() { + mWindowManager.removeView(mPopupAnchor); + mPopupMenu.dismiss(); } /** @@ -712,14 +735,19 @@ class NavigationBarApps extends LinearLayout { mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() { @Override public void onDismiss(PopupMenu menu) { + // FYU: thorough testing for closing menu either by the user or via + // shutdownPopupMenu() called at various moments of the menu creation, revealed that + // 'onDismiss' is guaranteed to be called after each invocation of showPopupMenu. mWindowManager.removeView(mPopupAnchor); anchorButton.removeOnAttachStateChangeListener(onAttachStateChangeListener); mPopupMenu.setOnDismissListener(null); mPopupMenu.getMenu().clear(); + mIsPopupInUse = false; } }); mWindowManager.addView(mPopupAnchor, mPopupAnchorLayoutParams); + mIsPopupInUse = true; } private void activateTask(int taskPersistentId) { @@ -740,10 +768,6 @@ class NavigationBarApps extends LinearLayout { private void populateLaunchMenu(AppButtonData appButtonData) { Menu menu = mPopupMenu.getMenu(); int taskCount = appButtonData.getTaskCount(); - if (taskCount == 0) { - menu.add("- TBD MENU ITEM -"); // adding something so that the menu is not empty. - return; - } for (int i = 0; i < taskCount; ++i) { final RecentTaskInfo taskInfo = appButtonData.tasks.get(i); MenuItem item = menu.add(getActivityForTask(taskInfo).flattenToShortString()); @@ -758,10 +782,14 @@ class NavigationBarApps extends LinearLayout { } /** - * Shows a task selection menu for an apps icon. + * Shows a task selection menu for clicked or hovered-over apps that have more than 1 running + * tasks. */ - private void showLaunchMenu(ImageView appIcon) { + void maybeShowLaunchMenu(ImageView appIcon) { + if (mIsPopupInUse) return; AppButtonData appButtonData = (AppButtonData) appIcon.getTag(); + if (appButtonData.getTaskCount() <= 1) return; + populateLaunchMenu(appButtonData); showPopupMenu(appIcon); } @@ -779,8 +807,7 @@ class NavigationBarApps extends LinearLayout { mShowMenuCallback = new Runnable() { @Override public void run() { - if (isPopupInUse()) return; - showLaunchMenu((ImageView) v); + maybeShowLaunchMenu((ImageView) v); } }; } @@ -824,17 +851,6 @@ class NavigationBarApps extends LinearLayout { mContext.startActivityAsUser(launchIntent, optsBundle, appInfo.getUser()); } - /** - * Shows a task selection menu for clicked apps that have more than 1 running tasks. - */ - void maybeShowLaunchMenu(ImageView appIcon) { - if (isPopupInUse()) return; - AppButtonData appButtonData = (AppButtonData) appIcon.getTag(); - if (appButtonData.getTaskCount() <= 1) return; - - showLaunchMenu(appIcon); - } - @Override public void onClick(View v) { AppButtonData appButtonData = (AppButtonData) v.getTag(); @@ -901,7 +917,7 @@ class NavigationBarApps extends LinearLayout { @Override public boolean onContextClick(View v) { - if (isPopupInUse()) return true; + if (mIsPopupInUse) return true; ImageView appIcon = (ImageView) v; populateContextMenu(appIcon); showPopupMenu(appIcon); @@ -1095,4 +1111,11 @@ class NavigationBarApps extends LinearLayout { }); } } + + @Override + public void onPinnedAppsChanged() { + if (getPinnedApps().equals(sAppsModel.getApps())) return; + recreatePinnedAppButtons(); + updateRecentApps(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java index 832a3e9f38bb..badb846694c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java @@ -44,6 +44,10 @@ import java.util.Set; * ComponentName. */ class NavigationBarAppsModel { + public interface OnAppsChangedListener { + void onPinnedAppsChanged(); + } + private final static String TAG = "NavigationBarAppsModel"; // Default number of apps to load initially. @@ -79,6 +83,9 @@ class NavigationBarAppsModel { // Apps are represented as an ordered list of app infos. private List<AppInfo> mApps = new ArrayList<AppInfo>(); + private List<OnAppsChangedListener> mOnAppsChangedListeners = + new ArrayList<OnAppsChangedListener>(); + // Id of the current user. private int mCurrentUserId = -1; @@ -167,6 +174,14 @@ class NavigationBarAppsModel { return null; } + public void addOnAppsChangedListener(OnAppsChangedListener listener) { + mOnAppsChangedListeners.add(listener); + } + + public void removeOnAppsChangedListener(OnAppsChangedListener listener) { + mOnAppsChangedListeners.remove(listener); + } + /** * Reinitializes the model for a new user. */ @@ -239,6 +254,11 @@ class NavigationBarAppsModel { public void setApps(List<AppInfo> apps) { mApps = apps; savePrefs(); + + int size = mOnAppsChangedListeners.size(); + for (int i = 0; i < size; ++i) { + mOnAppsChangedListeners.get(i).onPinnedAppsChanged(); + } } /** Saves the current model to disk. */ 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 d3af8f03fa73..ea6681e48537 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -315,6 +315,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, boolean mLeaveOpenOnKeyguardHide; KeyguardIndicationController mKeyguardIndicationController; + // Keyguard is going away soon. + private boolean mKeyguardGoingAway; + // Keyguard is actually fading away now. private boolean mKeyguardFadingAway; private long mKeyguardFadingAwayDelay; private long mKeyguardFadingAwayDuration; @@ -488,12 +491,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private boolean mLaunchTransitionFadingAway; private ExpandableNotificationRow mDraggedDownRow; private boolean mLaunchCameraOnScreenTurningOn; + private boolean mLaunchCameraOnFinishedGoingToSleep; private PowerManager.WakeLock mGestureWakeLock; private Vibrator mVibrator; // Fingerprint (as computed by getLoggingFingerprint() of the last logged state. private int mLastLoggedStateFingerprint; + /** + * If set, the device has started going to sleep but isn't fully non-interactive yet. + */ + protected boolean mStartedGoingToSleep; + private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_CARD | StackViewState.LOCATION_MAIN_AREA; @@ -3596,6 +3605,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Treat Keyguard exit animation as an app transition to achieve nice transition for status // bar. + mKeyguardGoingAway = true; mIconController.appTransitionPending(); } @@ -3627,6 +3637,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, */ public void finishKeyguardFadingAway() { mKeyguardFadingAway = false; + mKeyguardGoingAway = false; } public void stopWaitingForKeyguardExit() { @@ -3958,16 +3969,33 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, disable(mDisabledUnmodified1, mDisabledUnmodified2, true /* animate */); } + public void onStartedGoingToSleep() { + mStartedGoingToSleep = true; + } + public void onFinishedGoingToSleep() { mNotificationPanel.onAffordanceLaunchEnded(); releaseGestureWakeLock(); mLaunchCameraOnScreenTurningOn = false; + mStartedGoingToSleep = false; mDeviceInteractive = false; mWakeUpComingFromTouch = false; mWakeUpTouchLocation = null; mLockedPhoneAnalytics.onScreenOff(); mStackScroller.setAnimationsEnabled(false); updateVisibleToUser(); + if (mLaunchCameraOnFinishedGoingToSleep) { + mLaunchCameraOnFinishedGoingToSleep = false; + + // This gets executed before we will show Keyguard, so post it in order that the state + // is correct. + mHandler.post(new Runnable() { + @Override + public void run() { + onCameraLaunchGestureDetected(); + } + }); + } } public void onStartedWakingUp() { @@ -3988,7 +4016,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void vibrateForCameraGesture() { - mVibrator.vibrate(750L); + // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep. + mVibrator.vibrate(new long[] { 0, 750L }, -1 /* repeat */); } public void onScreenTurnedOn() { @@ -4137,9 +4166,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void appTransitionStarting(long startTime, long duration) { // Use own timings when Keyguard is going away, see keyguardGoingAway and - // setKeyguardFadingAway. When duration is 0, skip this one because no animation is really - // playing. - if (!mKeyguardFadingAway && duration > 0) { + // setKeyguardFadingAway. + if (!mKeyguardGoingAway) { mIconController.appTransitionStarting(startTime, duration); } if (mIconPolicy != null) { @@ -4149,6 +4177,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, @Override public void onCameraLaunchGestureDetected() { + if (mStartedGoingToSleep) { + mLaunchCameraOnFinishedGoingToSleep = true; + return; + } if (!mNotificationPanel.canCameraGestureBeLaunched( mStatusBarKeyguardViewManager.isShowing() && mExpandedVisible)) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index e26f42301830..394ff3f8245c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -164,6 +164,10 @@ public class StatusBarKeyguardViewManager { } } + public void onStartedGoingToSleep() { + mPhoneStatusBar.onStartedGoingToSleep(); + } + public void onFinishedGoingToSleep() { mDeviceInteractive = false; mPhoneStatusBar.onFinishedGoingToSleep(); diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java index af7ee0856cb6..f156607fb26d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java @@ -139,6 +139,7 @@ public class ZenFooter extends LinearLayout { } public void onConfigurationChanged() { + mEndNowButton.setText(mContext.getString(R.string.volume_zen_end_now)); mSpTexts.update(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java index e7a40d7fc10b..0ae0cf674644 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java @@ -412,4 +412,19 @@ public class NavigationBarAppsModelTest extends AndroidTestCase { verify(mMockEdit).apply(); verifyNoMoreInteractions(mMockEdit); } + + /** Tests the apps-changed listener. */ + public void testAppsChangedListeners() { + NavigationBarAppsModel.OnAppsChangedListener listener = + mock(NavigationBarAppsModel.OnAppsChangedListener.class); + + mModel.addOnAppsChangedListener(listener); + mModel.setApps(new ArrayList<AppInfo>()); + verify(listener).onPinnedAppsChanged(); + verifyNoMoreInteractions(listener); + + mModel.removeOnAppsChangedListener(listener); + mModel.setApps(new ArrayList<AppInfo>()); + verifyNoMoreInteractions(listener); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 7051b52cad4e..91c3d48d1274 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -650,6 +650,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mIsTouchExplorationEnabled = false; userState.mIsEnhancedWebAccessibilityEnabled = false; userState.mIsDisplayMagnificationEnabled = false; + userState.mIsAutoclickEnabled = false; userState.mInstalledServices.add(accessibilityServiceInfo); userState.mEnabledServices.clear(); userState.mEnabledServices.add(sFakeAccessibilityServiceComponentName); @@ -700,6 +701,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mIsTouchExplorationEnabled = touchExplorationEnabled; userState.mIsEnhancedWebAccessibilityEnabled = false; userState.mIsDisplayMagnificationEnabled = false; + userState.mIsAutoclickEnabled = false; userState.mEnabledServices.clear(); userState.mEnabledServices.add(service); userState.mBindingServices.clear(); @@ -1281,6 +1283,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (userState.mIsFilterKeyEventsEnabled) { flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; } + if (userState.mIsAutoclickEnabled) { + flags |= AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK; + } if (flags != 0) { if (!mHasInputFilter) { mHasInputFilter = true; @@ -1485,6 +1490,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { somthingChanged |= readHighTextContrastEnabledSettingLocked(userState); somthingChanged |= readEnhancedWebAccessibilityEnabledChangedLocked(userState); somthingChanged |= readDisplayMagnificationEnabledSettingLocked(userState); + somthingChanged |= readAutoclickEnabledSettingLocked(userState); somthingChanged |= readDisplayColorAdjustmentSettingsLocked(userState); return somthingChanged; } @@ -1523,6 +1529,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } + private boolean readAutoclickEnabledSettingLocked(UserState userState) { + final boolean autoclickEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, + 0, userState.mUserId) == 1; + if (autoclickEnabled != userState.mIsAutoclickEnabled) { + userState.mIsAutoclickEnabled = autoclickEnabled; + return true; + } + return false; + } + private boolean readEnhancedWebAccessibilityEnabledChangedLocked(UserState userState) { final boolean enhancedWeAccessibilityEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, @@ -1671,6 +1689,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled); pw.append(", displayMagnificationEnabled=" + userState.mIsDisplayMagnificationEnabled); + pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled); if (userState.mUiAutomationService != null) { pw.append(", "); userState.mUiAutomationService.dump(fd, pw, args); @@ -3777,6 +3796,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public boolean mIsTextHighContrastEnabled; public boolean mIsEnhancedWebAccessibilityEnabled; public boolean mIsDisplayMagnificationEnabled; + public boolean mIsAutoclickEnabled; public boolean mIsFilterKeyEventsEnabled; public boolean mHasDisplayColorAdjustment; public boolean mAccessibilityFocusOnlyInActiveWindow; @@ -3841,6 +3861,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mIsTouchExplorationEnabled = false; mIsEnhancedWebAccessibilityEnabled = false; mIsDisplayMagnificationEnabled = false; + mIsAutoclickEnabled = false; } public void destroyUiAutomationService() { @@ -3865,6 +3886,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); + private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED); + private final Uri mEnabledAccessibilityServicesUri = Settings.Secure.getUriFor( Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); @@ -3897,6 +3921,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver(mAutoclickEnabledUri, + false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( @@ -3938,6 +3964,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (readDisplayMagnificationEnabledSettingLocked(userState)) { onUserStateChangedLocked(userState); } + } else if (mAutoclickEnabledUri.equals(uri)) { + if (readAutoclickEnabledSettingLocked(userState)) { + onUserStateChangedLocked(userState); + } } else if (mEnabledAccessibilityServicesUri.equals(uri)) { if (readEnabledAccessibilityServicesLocked(userState)) { onUserStateChangedLocked(userState); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index fa87270d6984..23850605a9c8 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -363,7 +363,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // ... and see if these are hosts we've been awaiting. // NOTE: We are backing up and restoring only the owner. - if (newPackageAdded && userId == UserHandle.USER_OWNER) { + // TODO: http://b/22388012 + if (newPackageAdded && userId == UserHandle.USER_SYSTEM) { final int uid = getUidForPackage(pkgName, userId); if (uid >= 0 ) { resolveHostUidLocked(pkgName, uid); @@ -2729,7 +2730,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Host host = lookupHostLocked(oldHostId); if (host != null) { final int uid = getUidForPackage(NEW_KEYGUARD_HOST_PACKAGE, - UserHandle.USER_OWNER); + UserHandle.USER_SYSTEM); if (uid >= 0) { host.id = new HostId(uid, KEYGUARD_HOST_ID, NEW_KEYGUARD_HOST_PACKAGE); } @@ -2750,7 +2751,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private static AtomicFile getSavedStateFile(int userId) { File dir = Environment.getUserSystemDirectory(userId); File settingsFile = getStateFile(userId); - if (!settingsFile.exists() && userId == UserHandle.USER_OWNER) { + if (!settingsFile.exists() && userId == UserHandle.USER_SYSTEM) { if (!dir.exists()) { dir.mkdirs(); } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index d5c4a41f07b2..fd67b4148b89 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -271,7 +271,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { int sysUiUid = -1; try { sysUiUid = mContext.getPackageManager().getPackageUid("com.android.systemui", - UserHandle.USER_OWNER); + UserHandle.USER_SYSTEM); } catch (PackageManager.NameNotFoundException e) { // Some platforms, such as wearables do not have a system ui. Log.w(TAG, "Unable to resolve SystemUI's UID.", e); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index d1e1683d6478..6190a5ab357e 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2270,8 +2270,9 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkRequestInfoLogs.log("REGISTER " + nri); if (!nri.isRequest) { for (NetworkAgentInfo network : mNetworkAgentInfos.values()) { - if (network.satisfiesImmutableCapabilitiesOf(nri.request)) { - updateSignalStrengthThresholds(network); + if (nri.request.networkCapabilities.hasSignalStrength() && + network.satisfiesImmutableCapabilitiesOf(nri.request)) { + updateSignalStrengthThresholds(network, "REGISTER", nri.request); } } } @@ -2388,8 +2389,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // if this listen request applies and remove it. for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { nai.networkRequests.remove(nri.request.requestId); - if (nai.satisfiesImmutableCapabilitiesOf(nri.request)) { - updateSignalStrengthThresholds(nai); + if (nri.request.networkCapabilities.hasSignalStrength() && + nai.satisfiesImmutableCapabilitiesOf(nri.request)) { + updateSignalStrengthThresholds(nai, "RELEASE", nri.request); } } } @@ -3639,9 +3641,24 @@ public class ConnectivityService extends IConnectivityManager.Stub return new ArrayList<Integer>(thresholds); } - private void updateSignalStrengthThresholds(NetworkAgentInfo nai) { + private void updateSignalStrengthThresholds( + NetworkAgentInfo nai, String reason, NetworkRequest request) { + ArrayList<Integer> thresholdsArray = getSignalStrengthThresholds(nai); Bundle thresholds = new Bundle(); - thresholds.putIntegerArrayList("thresholds", getSignalStrengthThresholds(nai)); + thresholds.putIntegerArrayList("thresholds", thresholdsArray); + + // TODO: Switch to VDBG. + if (DBG) { + String detail; + if (request != null && request.networkCapabilities.hasSignalStrength()) { + detail = reason + " " + request.networkCapabilities.getSignalStrength(); + } else { + detail = reason; + } + log(String.format("updateSignalStrengthThresholds: %s, sending %s to %s", + detail, Arrays.toString(thresholdsArray.toArray()), nai.name())); + } + nai.asyncChannel.sendMessage( android.net.NetworkAgent.CMD_SET_SIGNAL_STRENGTH_THRESHOLDS, 0, 0, thresholds); @@ -4624,7 +4641,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // so we could decide to tear it down immediately afterwards. That's fine though - on // disconnection NetworkAgents should stop any signal strength monitoring they have been // doing. - updateSignalStrengthThresholds(networkAgent); + updateSignalStrengthThresholds(networkAgent, "CONNECT", null); // Consider network even though it is not yet validated. rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP); diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 46fd28a62108..2f4ad3ff0e29 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -1689,7 +1689,7 @@ public class DeviceIdleController extends SystemService } if (args != null) { - int userId = UserHandle.USER_OWNER; + int userId = UserHandle.USER_SYSTEM; for (int i=0; i<args.length; i++) { String arg = args[i]; if ("-h".equals(arg)) { diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index 69f0cef6d39e..bd7d4b27e02c 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -101,8 +101,8 @@ public class GestureLauncherService extends SystemService { * Whether camera double tap power button gesture is currently enabled; */ private boolean mCameraDoubleTapPowerEnabled; - private long mLastPowerDownWhileNonInteractive = 0; - + private long mLastPowerDownWhileNonInteractive; + private long mLastPowerDownWhileInteractive; public GestureLauncherService(Context context) { super(context); @@ -251,23 +251,30 @@ public class GestureLauncherService extends SystemService { public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive) { boolean launched = false; + boolean intercept = false; synchronized (this) { if (!mCameraDoubleTapPowerEnabled) { mLastPowerDownWhileNonInteractive = 0; + mLastPowerDownWhileInteractive = 0; return false; } if (event.getEventTime() - mLastPowerDownWhileNonInteractive < CAMERA_POWER_DOUBLE_TAP_TIME_MS) { launched = true; + intercept = true; + } else if (event.getEventTime() - mLastPowerDownWhileInteractive + < CAMERA_POWER_DOUBLE_TAP_TIME_MS) { + launched = true; } mLastPowerDownWhileNonInteractive = interactive ? 0 : event.getEventTime(); + mLastPowerDownWhileInteractive = interactive ? event.getEventTime() : 0; } if (launched) { Slog.i(TAG, "Power button double tap gesture detected, launching camera."); launched = handleCameraLaunchGesture(false /* useWakelock */, MetricsLogger.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE); } - return launched; + return intercept && launched; } /** diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index b418abaae4d4..9dad7a181a06 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -541,7 +541,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub prevSubtypes.addAll(entry.getValue()); } - final String mergedImesAndSubtypesString = buildInputMethodsAndSubtypesString(prevMap); + final String mergedImesAndSubtypesString = + InputMethodUtils.buildInputMethodsAndSubtypesString(prevMap); if (DEBUG_RESTORE) { Slog.i(TAG, "Merged IME string:"); Slog.i(TAG, " " + mergedImesAndSubtypesString); @@ -550,23 +551,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Settings.Secure.ENABLED_INPUT_METHODS, mergedImesAndSubtypesString); } - // TODO: Move this method to InputMethodUtils with adding unit tests. - static String buildInputMethodsAndSubtypesString(ArrayMap<String, ArraySet<String>> map) { - // we want to use the canonical InputMethodSettings implementation, - // so we convert data structures first. - List<Pair<String, ArrayList<String>>> imeMap = new ArrayList<>(4); - for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) { - final String imeName = entry.getKey(); - final ArraySet<String> subtypeSet = entry.getValue(); - final ArrayList<String> subtypes = new ArrayList<>(2); - if (subtypeSet != null) { - subtypes.addAll(subtypeSet); - } - imeMap.add(new Pair<>(imeName, subtypes)); - } - return InputMethodSettings.buildInputMethodsSettingString(imeMap); - } - class MyPackageMonitor extends PackageMonitor { private boolean isChangingPackagesOfCurrentUser() { final int userId = getChangingUserId(); @@ -3504,7 +3488,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub throw new NullPointerException("methodMap is null"); } mMethodMap = methodMap; - final File systemDir = userId == UserHandle.USER_OWNER + final File systemDir = userId == UserHandle.USER_SYSTEM ? new File(Environment.getDataDirectory(), SYSTEM_PATH) : Environment.getUserSystemDirectory(userId); final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH); diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index c7e0c98ba9cf..da8152861edf 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -145,7 +145,8 @@ public class LockSettingsService extends ILockSettings.Stub { } catch (RemoteException e) { Slog.e(TAG, "Failure retrieving IGateKeeperService", e); } - mStorage.prefetchUser(UserHandle.USER_OWNER); + // TODO: maybe skip this for split system user mode. + mStorage.prefetchUser(UserHandle.USER_SYSTEM); } private void migrateOldData() { diff --git a/services/core/java/com/android/server/LockSettingsStrongAuth.java b/services/core/java/com/android/server/LockSettingsStrongAuth.java index c023f4aa17a0..0e4d5a7f2a7b 100644 --- a/services/core/java/com/android/server/LockSettingsStrongAuth.java +++ b/services/core/java/com/android/server/LockSettingsStrongAuth.java @@ -132,7 +132,7 @@ public class LockSettingsStrongAuth { } public void requireStrongAuth(int strongAuthReason, int userId) { - if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_OWNER) { + if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_SYSTEM) { mHandler.obtainMessage(MSG_REQUIRE_STRONG_AUTH, strongAuthReason, userId).sendToTarget(); } else { diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java index e0352e091af7..33f92344f615 100644 --- a/services/core/java/com/android/server/MmsServiceBroker.java +++ b/services/core/java/com/android/server/MmsServiceBroker.java @@ -500,7 +500,7 @@ public class MmsServiceBroker extends SystemService { private Uri adjustUriForUserAndGrantPermission(Uri contentUri, String action, int permission) { final int callingUserId = UserHandle.getCallingUserId(); - if (callingUserId != UserHandle.USER_OWNER) { + if (callingUserId != UserHandle.USER_SYSTEM) { contentUri = ContentProvider.maybeAddUserId(contentUri, callingUserId); } long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java index aace66c7745d..fc3a322670ee 100644 --- a/services/core/java/com/android/server/TextServicesManagerService.java +++ b/services/core/java/com/android/server/TextServicesManagerService.java @@ -99,7 +99,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { broadcastFilter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiver(new TextServicesBroadcastReceiver(), broadcastFilter); - int userId = UserHandle.USER_OWNER; + int userId = UserHandle.USER_SYSTEM; try { ActivityManagerNative.getDefault().registerUserSwitchObserver( new IUserSwitchObserver.Stub() { diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 30f4dce310ca..c2284224502d 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -59,6 +59,7 @@ public class VibratorService extends IVibratorService.Stub implements InputManager.InputDeviceListener { private static final String TAG = "VibratorService"; private static final boolean DEBUG = false; + private static final String SYSTEM_UI_PACKAGE = "com.android.systemui"; private final LinkedList<Vibration> mVibrations; private final LinkedList<VibrationInfo> mPreviousVibrations; @@ -147,7 +148,8 @@ public class VibratorService extends IVibratorService.Stub } public boolean isSystemHapticFeedback() { - return (mUid == Process.SYSTEM_UID || mUid == 0) && mRepeat < 0; + return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg)) + && mRepeat < 0; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 14376d1bdb80..b21e90279308 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19,7 +19,6 @@ package com.android.server.am; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; -import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.DOCKED_STACK_ID; import static android.app.ActivityManager.HOME_STACK_ID; import static android.app.ActivityManager.INVALID_STACK_ID; @@ -215,7 +214,6 @@ import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.Trace; import android.os.UpdateLock; import android.os.UserHandle; import android.os.UserManager; @@ -8652,7 +8650,7 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public void resizeTask(int taskId, Rect bounds) { + public void resizeTask(int taskId, Rect bounds, boolean resizedByUser) { enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, "resizeTask()"); long ident = Binder.clearCallingIdentity(); @@ -8663,7 +8661,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found"); return; } - mStackSupervisor.resizeTaskLocked(task, bounds); + mStackSupervisor.resizeTaskLocked(task, bounds, resizedByUser); } } finally { Binder.restoreCallingIdentity(ident); @@ -9053,6 +9051,35 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** + * Moves the input task to the docked stack. + * + * @param taskId Id of task to move. + * @param createMode The mode the docked stack should be created in if it doesn't exist + * already. See + * {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT} + * and + * {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT} + * @param toTop If the task and stack should be moved to the top. + */ + @Override + public void moveTaskToDockedStack(int taskId, int createMode, boolean toTop) { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "moveTaskToDockedStack()"); + synchronized (this) { + long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId + + " to createMode=" + createMode + " toTop=" + toTop); + mWindowManager.setDockedStackCreateMode(createMode); + mStackSupervisor.moveTaskToStackLocked( + taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + @Override public void resizeStack(int stackId, Rect bounds) { enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, @@ -17633,7 +17660,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (starting != null) { - kept = mainStack.ensureActivityConfigurationLocked(starting, changes); + kept = mainStack.ensureActivityConfigurationLocked(starting, changes, false); // And we need to make sure at this point that all other activities // are made visible with the correct configuration. mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index a8a007378d53..3351226172ea 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -1427,7 +1427,7 @@ final class ActivityStack { // First: if this is not the current activity being started, make // sure it matches the current configuration. if (r != starting) { - ensureActivityConfigurationLocked(r, 0); + ensureActivityConfigurationLocked(r, 0, false); } if (r.app == null || r.app.thread == null) { @@ -3500,7 +3500,7 @@ final class ActivityStack { final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) { if (DEBUG_SWITCH || DEBUG_CLEANUP) Slog.v(TAG_SWITCH, "Removing activity from " + reason + ": token=" + r - + ", app=" + (r.app != null ? r.app.processName : "(null)")); + + ", app=" + (r.app != null ? r.app.processName : "(null)")); EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, r.userId, System.identityHashCode(r), r.task.taskId, r.shortComponentName, reason); @@ -3977,7 +3977,8 @@ final class ActivityStack { * for whatever reason. Ensures the HistoryRecord is updated with the * correct configuration and all other bookkeeping is handled. */ - final boolean ensureActivityConfigurationLocked(ActivityRecord r, int globalChanges) { + final boolean ensureActivityConfigurationLocked(ActivityRecord r, int globalChanges, + boolean preserveWindow) { if (mConfigWillChange) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Skipping config check (will change): " + r); @@ -4062,12 +4063,14 @@ final class ActivityStack { // "restart!". if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is relaunching resumed " + r); - relaunchActivityLocked(r, r.configChangeFlags, true); + relaunchActivityLocked(r, r.configChangeFlags, true, + preserveWindow && isResizeOnlyChange(changes)); r.configChangeFlags = 0; } else { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is relaunching non-resumed " + r); - relaunchActivityLocked(r, r.configChangeFlags, false); + relaunchActivityLocked(r, r.configChangeFlags, false, + preserveWindow && isResizeOnlyChange(changes)); r.configChangeFlags = 0; } @@ -4160,7 +4163,13 @@ final class ActivityStack { return taskChanges; } - private boolean relaunchActivityLocked(ActivityRecord r, int changes, boolean andResume) { + private static boolean isResizeOnlyChange(int change) { + return (change & ~(ActivityInfo.CONFIG_SCREEN_SIZE + | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) == 0; + } + + private boolean relaunchActivityLocked(ActivityRecord r, int changes, boolean andResume, + boolean preserveWindow) { List<ResultInfo> results = null; List<ReferrerIntent> newIntents = null; if (andResume) { @@ -4169,7 +4178,7 @@ final class ActivityStack { } if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Relaunching: " + r + " with results=" + results + " newIntents=" + newIntents - + " andResume=" + andResume); + + " andResume=" + andResume + " preserveWindow=" + preserveWindow); EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY : EventLogTags.AM_RELAUNCH_ACTIVITY, r.userId, System.identityHashCode(r), r.task.taskId, r.shortComponentName); @@ -4184,7 +4193,7 @@ final class ActivityStack { r.forceNewConfig = false; r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes, !andResume, new Configuration(mService.mConfiguration), - new Configuration(r.task.mOverrideConfig)); + new Configuration(r.task.mOverrideConfig), preserveWindow); // Note: don't need to call pauseIfSleepingLocked() here, because // the caller will only pass in 'andResume' if this activity is // currently resumed, which implies we aren't sleeping. diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 8ab4ae59a201..d3cea8de1442 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3045,7 +3045,7 @@ public final class ActivityStackSupervisor implements DisplayListener { stack.setBounds(bounds); if (r != null) { - final boolean updated = stack.ensureActivityConfigurationLocked(r, 0); + final boolean updated = stack.ensureActivityConfigurationLocked(r, 0, false); // And we need to make sure at this point that all other activities // are made visible with the correct configuration. ensureActivitiesVisibleLocked(r, 0); @@ -3055,7 +3055,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - void resizeTaskLocked(TaskRecord task, Rect bounds) { + void resizeTaskLocked(TaskRecord task, Rect bounds, boolean resizedByUser) { if (!task.mResizeable) { Slog.w(TAG, "resizeTask: task " + task + " not resizeable."); return; @@ -3088,7 +3088,8 @@ public final class ActivityStackSupervisor implements DisplayListener { && stackId != FREEFORM_WORKSPACE_STACK_ID && stackId != DOCKED_STACK_ID) { stackId = FREEFORM_WORKSPACE_STACK_ID; } - if (stackId != task.stack.mStackId) { + final boolean changedStacks = stackId != task.stack.mStackId; + if (changedStacks) { moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, "resizeTask"); } @@ -3101,20 +3102,22 @@ public final class ActivityStackSupervisor implements DisplayListener { ActivityRecord r = task.topRunningActivityLocked(null); if (r != null) { final ActivityStack stack = task.stack; - kept = stack.ensureActivityConfigurationLocked(r, 0); + final boolean preserveWindow = resizedByUser && !changedStacks; + kept = stack.ensureActivityConfigurationLocked(r, 0, preserveWindow); // All other activities must be made visible with their correct configuration. ensureActivitiesVisibleLocked(r, 0); if (!kept) { resumeTopActivitiesLocked(stack, null, null); - // We are about to relaunch the activity because its configuration changed due - // to size change. The activity will first remove the old window and then add a - // new one. This call will tell window manager about this, so it can preserve - // the old window until the new one is drawn. This prevents having a gap between - // the removal and addition, in which no window is visible. If we also changed - // the stack to the fullscreen stack, i.e. maximized the window, we will animate - // the transition. - mWindowManager.setReplacingWindow(r.appToken, - stackId == FULLSCREEN_WORKSPACE_STACK_ID /* animate */); + if (changedStacks && stackId == FULLSCREEN_WORKSPACE_STACK_ID) { + // We are about to relaunch the activity because its configuration changed + // due to being maximized, i.e. size change. The activity will first + // remove the old window and then add a new one. This call will tell window + // manager about this, so it can preserve the old window until the new + // one is drawn. This prevents having a gap between the removal and + // addition, in which no window is visible. We also want the entrace of the + // new window to be properly animated. + mWindowManager.setReplacingWindow(r.appToken, true /* animate */); + } } } } @@ -3240,12 +3243,12 @@ public final class ActivityStackSupervisor implements DisplayListener { // Make sure the task has the appropriate bounds/size for the stack it is in. if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) { - resizeTaskLocked(task, stack.mBounds); + resizeTaskLocked(task, stack.mBounds, false); } else if (stackId == FREEFORM_WORKSPACE_STACK_ID && task.mBounds == null && task.mLastNonFullscreenBounds != null) { - resizeTaskLocked(task, task.mLastNonFullscreenBounds); + resizeTaskLocked(task, task.mLastNonFullscreenBounds, false); } else if (stackId == DOCKED_STACK_ID) { - resizeTaskLocked(task, stack.mBounds); + resizeTaskLocked(task, stack.mBounds, false); } // The task might have already been running and its visibility needs to be synchronized with diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java index 814e8b42e9d7..424ceb1a457c 100644 --- a/services/core/java/com/android/server/am/CompatModePackages.java +++ b/services/core/java/com/android/server/am/CompatModePackages.java @@ -344,7 +344,7 @@ public final class CompatModePackages { } if (starting != null) { - stack.ensureActivityConfigurationLocked(starting, 0); + stack.ensureActivityConfigurationLocked(starting, 0, false); // And we need to make sure at this point that all other activities // are made visible with the correct configuration. stack.ensureActivitiesVisibleLocked(starting, 0); diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java index ed8b1dd7fcd5..878c0e7aba50 100644 --- a/services/core/java/com/android/server/am/ProviderMap.java +++ b/services/core/java/com/android/server/am/ProviderMap.java @@ -211,7 +211,7 @@ public final class ProviderMap { boolean doit, boolean evenPersistent, int userId, ArrayList<ContentProviderRecord> result) { boolean didSomething = false; - if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_OWNER) { + if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_SYSTEM) { didSomething = collectPackageProvidersLocked(packageName, filterByClasses, doit, evenPersistent, mSingletonByClass, result); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1475f2f292cc..99ca050c9ae7 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1560,7 +1560,7 @@ public class AudioService extends IAudioService.Stub { } finally { Binder.restoreCallingIdentity(ident); } - return UserHandle.USER_OWNER; + return UserHandle.USER_SYSTEM; } // UI update and Broadcast Intent diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java index aca699152e02..5fd39c02a10a 100644 --- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java +++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java @@ -18,6 +18,7 @@ package com.android.server.connectivity; import static android.system.OsConstants.*; +import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkUtils; @@ -27,6 +28,7 @@ import android.system.ErrnoException; import android.system.Os; import android.system.StructTimeval; import android.text.TextUtils; +import android.util.Pair; import com.android.internal.util.IndentingPrintWriter; @@ -149,6 +151,8 @@ public class NetworkDiagnostics { } private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>(); + private final Map<Pair<InetAddress, InetAddress>, Measurement> mExplicitSourceIcmpChecks = + new HashMap<>(); private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>(); private final String mDescription; @@ -178,7 +182,11 @@ public class NetworkDiagnostics { for (RouteInfo route : mLinkProperties.getRoutes()) { if (route.hasGateway()) { - prepareIcmpMeasurement(route.getGateway()); + InetAddress gateway = route.getGateway(); + prepareIcmpMeasurement(gateway); + if (route.isIPv6Default()) { + prepareExplicitSourceIcmpMeasurements(gateway); + } } } for (InetAddress nameserver : mLinkProperties.getDnsServers()) { @@ -213,6 +221,20 @@ public class NetworkDiagnostics { } } + private void prepareExplicitSourceIcmpMeasurements(InetAddress target) { + for (LinkAddress l : mLinkProperties.getLinkAddresses()) { + InetAddress source = l.getAddress(); + if (source instanceof Inet6Address && l.isGlobalPreferred()) { + Pair<InetAddress, InetAddress> srcTarget = new Pair<>(source, target); + if (!mExplicitSourceIcmpChecks.containsKey(srcTarget)) { + Measurement measurement = new Measurement(); + measurement.thread = new Thread(new IcmpCheck(source, target, measurement)); + mExplicitSourceIcmpChecks.put(srcTarget, measurement); + } + } + } + } + private void prepareDnsMeasurement(InetAddress target) { if (!mDnsUdpChecks.containsKey(target)) { Measurement measurement = new Measurement(); @@ -222,13 +244,16 @@ public class NetworkDiagnostics { } private int totalMeasurementCount() { - return mIcmpChecks.size() + mDnsUdpChecks.size(); + return mIcmpChecks.size() + mExplicitSourceIcmpChecks.size() + mDnsUdpChecks.size(); } private void startMeasurements() { for (Measurement measurement : mIcmpChecks.values()) { measurement.thread.start(); } + for (Measurement measurement : mExplicitSourceIcmpChecks.values()) { + measurement.thread.start(); + } for (Measurement measurement : mDnsUdpChecks.values()) { measurement.thread.start(); } @@ -261,6 +286,10 @@ public class NetworkDiagnostics { pw.println(entry.getValue().toString()); } } + for (Map.Entry<Pair<InetAddress, InetAddress>, Measurement> entry : + mExplicitSourceIcmpChecks.entrySet()) { + pw.println(entry.getValue().toString()); + } for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) { if (entry.getKey() instanceof Inet4Address) { pw.println(entry.getValue().toString()); @@ -276,13 +305,15 @@ public class NetworkDiagnostics { private class SimpleSocketCheck implements Closeable { + protected final InetAddress mSource; // Usually null. protected final InetAddress mTarget; protected final int mAddressFamily; protected final Measurement mMeasurement; protected FileDescriptor mFileDescriptor; protected SocketAddress mSocketAddress; - protected SimpleSocketCheck(InetAddress target, Measurement measurement) { + protected SimpleSocketCheck( + InetAddress source, InetAddress target, Measurement measurement) { mMeasurement = measurement; if (target instanceof Inet6Address) { @@ -301,6 +332,14 @@ public class NetworkDiagnostics { mTarget = target; mAddressFamily = AF_INET; } + + // We don't need to check the scope ID here because we currently only do explicit-source + // measurements from global IPv6 addresses. + mSource = source; + } + + protected SimpleSocketCheck(InetAddress target, Measurement measurement) { + this(null, target, measurement); } protected void setupSocket( @@ -314,6 +353,9 @@ public class NetworkDiagnostics { SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(readTimeout)); // TODO: Use IP_RECVERR/IPV6_RECVERR, pending OsContants availability. mNetwork.bindSocket(mFileDescriptor); + if (mSource != null) { + Os.bind(mFileDescriptor, mSource, 0); + } Os.connect(mFileDescriptor, mTarget, dstPort); mSocketAddress = Os.getsockname(mFileDescriptor); } @@ -343,8 +385,8 @@ public class NetworkDiagnostics { private final int mProtocol; private final int mIcmpType; - public IcmpCheck(InetAddress target, Measurement measurement) { - super(target, measurement); + public IcmpCheck(InetAddress source, InetAddress target, Measurement measurement) { + super(source, target, measurement); if (mAddressFamily == AF_INET6) { mProtocol = IPPROTO_ICMPV6; @@ -359,6 +401,10 @@ public class NetworkDiagnostics { mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}"; } + public IcmpCheck(InetAddress target, Measurement measurement) { + this(null, target, measurement); + } + @Override public void run() { // Check if this measurement has already failed during setup. diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index ea7d85e36944..ec7c1c437d92 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -337,7 +337,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe return; } stopPendingOperations(true); - mEnrollClient = new ClientMonitor(token, receiver, groupId, restricted); + mEnrollClient = new ClientMonitor(token, receiver, groupId, restricted, token.toString()); final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); try { final int result = daemon.enroll(cryptoToken, groupId, timeout); @@ -417,14 +417,15 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } void startAuthentication(IBinder token, long opId, int groupId, - IFingerprintServiceReceiver receiver, int flags, boolean restricted) { + IFingerprintServiceReceiver receiver, int flags, boolean restricted, + String opPackageName) { IFingerprintDaemon daemon = getFingerprintDaemon(); if (daemon == null) { Slog.w(TAG, "startAuthentication: no fingeprintd!"); return; } stopPendingOperations(true); - mAuthClient = new ClientMonitor(token, receiver, groupId, restricted); + mAuthClient = new ClientMonitor(token, receiver, groupId, restricted, opPackageName); if (inLockoutMode()) { Slog.v(TAG, "In lockout mode; disallowing authentication"); if (!mAuthClient.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { @@ -481,7 +482,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } stopPendingOperations(true); - mRemoveClient = new ClientMonitor(token, receiver, userId, restricted); + mRemoveClient = new ClientMonitor(token, receiver, userId, restricted, token.toString()); // The fingerprint template ids will be removed when we get confirmation from the HAL try { final int result = daemon.remove(fingerId, userId); @@ -574,11 +575,11 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } if (mAppOps.noteOp(AppOpsManager.OP_USE_FINGERPRINT, uid, opPackageName) != AppOpsManager.MODE_ALLOWED) { - Slog.v(TAG, "Rejecting " + opPackageName + " ; permission denied"); + Slog.w(TAG, "Rejecting " + opPackageName + " ; permission denied"); return false; } if (foregroundOnly && !isForegroundActivity(uid, pid)) { - Slog.v(TAG, "Rejecting " + opPackageName + " ; not in foreground"); + Slog.w(TAG, "Rejecting " + opPackageName + " ; not in foreground"); return false; } return true; @@ -606,13 +607,15 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe IFingerprintServiceReceiver receiver; int userId; boolean restricted; // True if client does not have MANAGE_FINGERPRINT permission + String owner; public ClientMonitor(IBinder token, IFingerprintServiceReceiver receiver, int userId, - boolean restricted) { + boolean restricted, String owner) { this.token = token; this.receiver = receiver; this.userId = userId; this.restricted = restricted; + this.owner = owner; // name of the client that owns this - for debugging try { token.linkToDeath(this, 0); } catch (RemoteException e) { @@ -695,6 +698,10 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe if (!authenticated) { receiver.onAuthenticationFailed(mHalDeviceId); } else { + if (DEBUG) { + Slog.v(TAG, "onAuthenticated(owner=" + mAuthClient.owner + + ", id=" + fpId + ", gp=" + groupId + ")"); + } Fingerprint fp = !restricted ? new Fingerprint("" /* TODO */, groupId, fpId, mHalDeviceId) : null; receiver.onAuthenticationSucceeded(mHalDeviceId, fp); @@ -915,6 +922,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe final IFingerprintServiceReceiver receiver, final int flags, final String opPackageName) { if (!canUseFingerprint(opPackageName, true /* foregroundOnly */)) { + if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName); return; } @@ -927,7 +935,8 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @Override public void run() { MetricsLogger.histogram(mContext, "fingerprint_token", opId != 0L ? 1 : 0); - startAuthentication(token, opId, effectiveGroupId, receiver, flags, restricted); + startAuthentication(token, opId, effectiveGroupId, receiver, flags, restricted, + opPackageName); } }); } diff --git a/services/core/java/com/android/server/firewall/SenderPackageFilter.java b/services/core/java/com/android/server/firewall/SenderPackageFilter.java index ec9b5ded511e..dc3edd9c7888 100644 --- a/services/core/java/com/android/server/firewall/SenderPackageFilter.java +++ b/services/core/java/com/android/server/firewall/SenderPackageFilter.java @@ -44,7 +44,9 @@ public class SenderPackageFilter implements Filter { int packageUid = -1; try { - packageUid = pm.getPackageUid(mPackageName, UserHandle.USER_OWNER); + // USER_SYSTEM here is not important. Only app id is used and getPackageUid() will + // return a uid whether the app is installed for a user or not. + packageUid = pm.getPackageUid(mPackageName, UserHandle.USER_SYSTEM); } catch (RemoteException ex) { // handled below } diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java index 5e5a55cc2249..bc9f5205e1c5 100644 --- a/services/core/java/com/android/server/location/GpsLocationProvider.java +++ b/services/core/java/com/android/server/location/GpsLocationProvider.java @@ -22,8 +22,6 @@ import com.android.internal.location.GpsNetInitiatedHandler; import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; -import com.android.internal.telephony.Phone; -import com.android.internal.telephony.PhoneConstants; import android.app.AlarmManager; import android.app.AppOpsManager; @@ -50,7 +48,10 @@ import android.location.LocationManager; import android.location.LocationProvider; import android.location.LocationRequest; import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; +import android.net.NetworkRequest; import android.net.Uri; import android.os.AsyncTask; import android.os.BatteryStats; @@ -199,6 +200,8 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final int DOWNLOAD_XTRA_DATA_FINISHED = 11; private static final int SUBSCRIPTION_OR_SIM_CHANGED = 12; private static final int INITIALIZE_HANDLER = 13; + private static final int REQUEST_SUPL_CONNECTION = 14; + private static final int RELEASE_SUPL_CONNECTION = 15; // Request setid private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1; @@ -295,9 +298,6 @@ public class GpsLocationProvider implements LocationProviderInterface { // true if we are enabled, protected by this private boolean mEnabled; - // true if we have network connectivity - private boolean mNetworkAvailable; - // states for injecting ntp and downloading xtra data private static final int STATE_PENDING_NETWORK = 0; private static final int STATE_DOWNLOADING = 1; @@ -372,10 +372,11 @@ public class GpsLocationProvider implements LocationProviderInterface { // Handler for processing events private Handler mHandler; - private String mAGpsApn; - private int mApnIpType; + /** It must be accessed only inside {@link #mHandler}. */ private int mAGpsDataConnectionState; + /** It must be accessed only inside {@link #mHandler}. */ private InetAddress mAGpsDataConnectionIpAddr; + private final ConnectivityManager mConnMgr; private final GpsNetInitiatedHandler mNIHandler; @@ -431,6 +432,42 @@ public class GpsLocationProvider implements LocationProviderInterface { return mGpsNavigationMessageProvider; } + /** + * Callback used to listen for data connectivity changes. + */ + private final ConnectivityManager.NetworkCallback mNetworkConnectivityCallback = + new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(Network network) { + requestUtcTime(); + xtraDownloadRequest(); + } + }; + + /** + * Callback used to listen for availability of a requested SUPL connection. + * It is kept as a separate instance from {@link #mNetworkConnectivityCallback} to be able to + * manage the registration/un-registration lifetimes separate. + */ + private final ConnectivityManager.NetworkCallback mSuplConnectivityCallback = + new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(Network network) { + sendMessage(UPDATE_NETWORK_STATE, 0 /*arg*/, network); + } + + @Override + public void onLost(Network network) { + releaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN); + } + + @Override + public void onUnavailable() { + // timeout, it was not possible to establish the required connection + releaseSuplConnection(GPS_AGPS_DATA_CONN_FAILED); + } + }; + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -447,16 +484,6 @@ public class GpsLocationProvider implements LocationProviderInterface { checkSmsSuplInit(intent); } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) { checkWapSuplInit(intent); - } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) - || action.equals(ConnectivityManager.CONNECTIVITY_ACTION_SUPL)) { - // retrieve NetworkType result for this UID - int networkType = intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, -1); - if (DEBUG) Log.d(TAG, "Connectivity action, type=" + networkType); - if (networkType == ConnectivityManager.TYPE_MOBILE - || networkType == ConnectivityManager.TYPE_WIFI - || networkType == ConnectivityManager.TYPE_MOBILE_SUPL) { - updateNetworkState(networkType); - } } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action) || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action) || Intent.ACTION_SCREEN_OFF.equals(action) @@ -574,7 +601,7 @@ public class GpsLocationProvider implements LocationProviderInterface { native_configuration_update(baos.toString()); Log.d(TAG, "final config = " + baos.toString()); } catch (IOException ex) { - Log.w(TAG, "failed to dump properties contents"); + Log.e(TAG, "failed to dump properties contents"); } } else if (DEBUG) { Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not" @@ -740,78 +767,117 @@ public class GpsLocationProvider implements LocationProviderInterface { return PROPERTIES; } - public void updateNetworkState(int networkType) { - sendMessage(UPDATE_NETWORK_STATE, networkType, null /*obj*/); - } - - private void handleUpdateNetworkState(int networkType) { - ConnectivityManager connectivityManager = (ConnectivityManager) mContext - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo mobileInfo = - connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); - NetworkInfo wifiInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); - mNetworkAvailable = (mobileInfo != null && mobileInfo.isConnected()) - || (wifiInfo != null && wifiInfo.isConnected()); - NetworkInfo info = connectivityManager.getNetworkInfo(networkType); - if (DEBUG) { - Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable") - + " info: " + info); + private void handleUpdateNetworkState(Network network) { + // retrieve NetworkInfo for this UID + NetworkInfo info = mConnMgr.getNetworkInfo(network); + if (info == null) { + return; } - if (info != null) { - if (native_is_agps_ril_supported()) { - boolean dataEnabled = TelephonyManager.getDefault().getDataEnabled(); - boolean networkAvailable = info.isAvailable() && dataEnabled; - String defaultApn = getSelectedApn(); - if (defaultApn == null) { - defaultApn = "dummy-apn"; - } - - native_update_network_state(info.isConnected(), info.getType(), - info.isRoaming(), networkAvailable, - info.getExtraInfo(), defaultApn); - } else if (DEBUG) { - Log.d(TAG, "Skipped network state update because AGPS-RIL in GPS HAL is not" - + " supported"); + boolean isConnected = info.isConnected(); + if (DEBUG) { + String message = String.format( + "UpdateNetworkState, state=%s, connected=%s, info=%s, capabilities=%S", + agpsDataConnStateAsString(), + isConnected, + info, + mConnMgr.getNetworkCapabilities(network)); + Log.d(TAG, message); + } + + if (native_is_agps_ril_supported()) { + boolean dataEnabled = TelephonyManager.getDefault().getDataEnabled(); + boolean networkAvailable = info.isAvailable() && dataEnabled; + String defaultApn = getSelectedApn(); + if (defaultApn == null) { + defaultApn = "dummy-apn"; } + + native_update_network_state( + isConnected, + info.getType(), + info.isRoaming(), + networkAvailable, + info.getExtraInfo(), + defaultApn); + } else if (DEBUG) { + Log.d(TAG, "Skipped network state update because GPS HAL AGPS-RIL is not supported"); } - if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL - && mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { - if (info.isConnected()) { + if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { + if (isConnected) { String apnName = info.getExtraInfo(); if (apnName == null) { - /* Assign a dummy value in the case of C2K as otherwise we will have a runtime - exception in the following call to native_agps_data_conn_open*/ + // assign a dummy value in the case of C2K as otherwise we will have a runtime + // exception in the following call to native_agps_data_conn_open apnName = "dummy-apn"; } - mAGpsApn = apnName; - mApnIpType = getApnIpType(apnName); + int apnIpType = getApnIpType(apnName); setRouting(); if (DEBUG) { String message = String.format( "native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s", - mAGpsApn, mApnIpType); + apnName, + apnIpType); Log.d(TAG, message); } - native_agps_data_conn_open(mAGpsApn, mApnIpType); + native_agps_data_conn_open(apnName, apnIpType); mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; } else { - Log.e(TAG, "call native_agps_data_conn_failed, info: " + info); - mAGpsApn = null; - mApnIpType = APN_INVALID; - mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; - native_agps_data_conn_failed(); + handleReleaseSuplConnection(GPS_AGPS_DATA_CONN_FAILED); } } + } - if (mNetworkAvailable) { - if (mInjectNtpTimePending == STATE_PENDING_NETWORK) { - sendMessage(INJECT_NTP_TIME, 0, null); - } - if (mDownloadXtraDataPending == STATE_PENDING_NETWORK) { - sendMessage(DOWNLOAD_XTRA_DATA, 0, null); - } + private void handleRequestSuplConnection(InetAddress address) { + if (DEBUG) { + String message = String.format( + "requestSuplConnection, state=%s, address=%s", + agpsDataConnStateAsString(), + address); + Log.d(TAG, message); + } + + if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) { + return; + } + mAGpsDataConnectionIpAddr = address; + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING; + + NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder(); + requestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + requestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL); + NetworkRequest request = requestBuilder.build(); + mConnMgr.requestNetwork( + request, + mSuplConnectivityCallback, + ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS); + } + + private void handleReleaseSuplConnection(int agpsDataConnStatus) { + if (DEBUG) { + String message = String.format( + "releaseSuplConnection, state=%s, status=%s", + agpsDataConnStateAsString(), + agpsDataConnStatusAsString(agpsDataConnStatus)); + Log.d(TAG, message); + } + + if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_CLOSED) { + return; + } + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + + mConnMgr.unregisterNetworkCallback(mSuplConnectivityCallback); + switch (agpsDataConnStatus) { + case GPS_AGPS_DATA_CONN_FAILED: + native_agps_data_conn_failed(); + break; + case GPS_RELEASE_AGPS_DATA_CONN: + native_agps_data_conn_closed(); + break; + default: + Log.e(TAG, "Invalid status to release SUPL connection: " + agpsDataConnStatus); } } @@ -820,7 +886,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // already downloading data return; } - if (!mNetworkAvailable) { + if (!isDataNetworkConnected()) { // try again when network is up mInjectNtpTimePending = STATE_PENDING_NETWORK; return; @@ -888,7 +954,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // already downloading data return; } - if (!mNetworkAvailable) { + if (!isDataNetworkConnected()) { // try again when network is up mDownloadXtraDataPending = STATE_PENDING_NETWORK; return; @@ -903,9 +969,7 @@ public class GpsLocationProvider implements LocationProviderInterface { GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mProperties); byte[] data = xtraDownloader.downloadXtraData(); if (data != null) { - if (DEBUG) { - Log.d(TAG, "calling native_inject_xtra_data"); - } + if (DEBUG) Log.d(TAG, "calling native_inject_xtra_data"); native_inject_xtra_data(data, data.length); mXtraBackOff.reset(); } @@ -1209,7 +1273,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if ("delete_aiding_data".equals(command)) { result = deleteAidingData(extras); } else if ("force_time_injection".equals(command)) { - sendMessage(INJECT_NTP_TIME, 0, null); + requestUtcTime(); result = true; } else if ("force_xtra_injection".equals(command)) { if (mSupportsXtra) { @@ -1536,51 +1600,20 @@ public class GpsLocationProvider implements LocationProviderInterface { case GPS_REQUEST_AGPS_DATA_CONN: if (DEBUG) Log.d(TAG, "GPS_REQUEST_AGPS_DATA_CONN"); Log.v(TAG, "Received SUPL IP addr[]: " + ipaddr); - // Set mAGpsDataConnectionState before calling startUsingNetworkFeature - // to avoid a race condition with handleUpdateNetworkState() - mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING; - int result = mConnMgr.startUsingNetworkFeature( - ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL); + InetAddress connectionIpAddress = null; if (ipaddr != null) { try { - mAGpsDataConnectionIpAddr = InetAddress.getByAddress(ipaddr); - Log.v(TAG, "IP address converted to: " + mAGpsDataConnectionIpAddr); + connectionIpAddress = InetAddress.getByAddress(ipaddr); + if (DEBUG) Log.d(TAG, "IP address converted to: " + connectionIpAddress); } catch (UnknownHostException e) { Log.e(TAG, "Bad IP Address: " + ipaddr, e); - mAGpsDataConnectionIpAddr = null; - } - } - - if (result == PhoneConstants.APN_ALREADY_ACTIVE) { - if (DEBUG) Log.d(TAG, "PhoneConstants.APN_ALREADY_ACTIVE"); - if (mAGpsApn != null) { - setRouting(); - native_agps_data_conn_open(mAGpsApn, mApnIpType); - mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; - } else { - Log.e(TAG, "mAGpsApn not set when receiving PhoneConstants.APN_ALREADY_ACTIVE"); - mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; - native_agps_data_conn_failed(); } - } else if (result == PhoneConstants.APN_REQUEST_STARTED) { - if (DEBUG) Log.d(TAG, "PhoneConstants.APN_REQUEST_STARTED"); - // Nothing to do here - } else { - if (DEBUG) Log.d(TAG, "startUsingNetworkFeature failed, value is " + - result); - mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; - native_agps_data_conn_failed(); } + sendMessage(REQUEST_SUPL_CONNECTION, 0 /*arg*/, connectionIpAddress); break; case GPS_RELEASE_AGPS_DATA_CONN: if (DEBUG) Log.d(TAG, "GPS_RELEASE_AGPS_DATA_CONN"); - if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) { - mConnMgr.stopUsingNetworkFeature( - ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL); - native_agps_data_conn_closed(); - mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; - mAGpsDataConnectionIpAddr = null; - } + releaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN); break; case GPS_AGPS_DATA_CONNECTED: if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONNECTED"); @@ -1596,6 +1629,10 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + private void releaseSuplConnection(int connStatus) { + sendMessage(RELEASE_SUPL_CONNECTION, connStatus, null /*obj*/); + } + /** * called from native code to report NMEA data received */ @@ -1921,8 +1958,8 @@ public class GpsLocationProvider implements LocationProviderInterface { /** * Called from native code to request utc time info */ - private void requestUtcTime() { + if (DEBUG) Log.d(TAG, "utcTimeRequest"); sendMessage(INJECT_NTP_TIME, 0, null); } @@ -1990,7 +2027,13 @@ public class GpsLocationProvider implements LocationProviderInterface { handleSetRequest(gpsRequest.request, gpsRequest.source); break; case UPDATE_NETWORK_STATE: - handleUpdateNetworkState(msg.arg1); + handleUpdateNetworkState((Network) msg.obj); + break; + case REQUEST_SUPL_CONNECTION: + handleRequestSuplConnection((InetAddress) msg.obj); + break; + case RELEASE_SUPL_CONNECTION: + handleReleaseSuplConnection(msg.arg1); break; case INJECT_NTP_TIME: handleInjectNtpTime(); @@ -2007,13 +2050,13 @@ public class GpsLocationProvider implements LocationProviderInterface { mDownloadXtraDataPending = STATE_IDLE; break; case UPDATE_LOCATION: - handleUpdateLocation((Location)msg.obj); + handleUpdateLocation((Location) msg.obj); break; case SUBSCRIPTION_OR_SIM_CHANGED: subscriptionOrSimChanged(mContext); break; case INITIALIZE_HANDLER: - initialize(); + handleInitialize(); break; } if (msg.arg2 == 1) { @@ -2027,7 +2070,7 @@ public class GpsLocationProvider implements LocationProviderInterface { * It is in charge of loading properties and registering for events that will be posted to * this handler. */ - private void initialize() { + private void handleInitialize() { // load default GPS configuration // (this configuration might change in the future based on SIM changes) reloadGpsProperties(mContext, mProperties); @@ -2067,8 +2110,6 @@ public class GpsLocationProvider implements LocationProviderInterface { intentFilter = new IntentFilter(); intentFilter.addAction(ALARM_WAKEUP); intentFilter.addAction(ALARM_TIMEOUT); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_SUPL); intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); @@ -2076,6 +2117,14 @@ public class GpsLocationProvider implements LocationProviderInterface { intentFilter.addAction(SIM_STATE_CHANGED); mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this); + // register for connectivity change events, this is equivalent to the deprecated way of + // registering for CONNECTIVITY_ACTION broadcasts + NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); + networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + NetworkRequest networkRequest = networkRequestBuilder.build(); + mConnMgr.registerNetworkCallback(networkRequest, mNetworkConnectivityCallback); + // listen for PASSIVE_PROVIDER updates LocationManager locManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); @@ -2140,15 +2189,11 @@ public class GpsLocationProvider implements LocationProviderInterface { } private int getApnIpType(String apn) { + ensureInHandlerThread(); if (apn == null) { return APN_INVALID; } - // look for cached data to use - if (apn.equals(mAGpsApn) && mApnIpType != APN_INVALID) { - return mApnIpType; - } - String selection = String.format("current = 1 and apn = '%s' and carrier_enabled = 1", apn); Cursor cursor = null; try { @@ -2197,6 +2242,7 @@ public class GpsLocationProvider implements LocationProviderInterface { return; } + // TODO: replace the use of this deprecated API boolean result = mConnMgr.requestRouteToHostAddress( ConnectivityManager.TYPE_MOBILE_SUPL, mAGpsDataConnectionIpAddr); @@ -2208,6 +2254,61 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + /** + * @return {@code true} if there is a data network available for outgoing connections, + * {@code false} otherwise. + */ + private boolean isDataNetworkConnected() { + NetworkInfo activeNetworkInfo = mConnMgr.getActiveNetworkInfo(); + return activeNetworkInfo != null && activeNetworkInfo.isConnected(); + } + + /** + * Ensures the calling function is running in the thread associated with {@link #mHandler}. + */ + private void ensureInHandlerThread() { + if (mHandler != null && Looper.myLooper() == mHandler.getLooper()) { + return; + } + throw new RuntimeException("This method must run on the Handler thread."); + } + + /** + * @return A string representing the current state stored in {@link #mAGpsDataConnectionState}. + */ + private String agpsDataConnStateAsString() { + switch(mAGpsDataConnectionState) { + case AGPS_DATA_CONNECTION_CLOSED: + return "CLOSED"; + case AGPS_DATA_CONNECTION_OPEN: + return "OPEN"; + case AGPS_DATA_CONNECTION_OPENING: + return "OPENING"; + default: + return "<Unknown>"; + } + } + + /** + * @return A string representing the given GPS_AGPS_DATA status. + */ + private String agpsDataConnStatusAsString(int agpsDataConnStatus) { + switch (agpsDataConnStatus) { + case GPS_AGPS_DATA_CONNECTED: + return "CONNECTED"; + case GPS_AGPS_DATA_CONN_DONE: + return "DONE"; + case GPS_AGPS_DATA_CONN_FAILED: + return "FAILED"; + case GPS_RELEASE_AGPS_DATA_CONN: + return "RELEASE"; + case GPS_REQUEST_AGPS_DATA_CONN: + return "REQUEST"; + default: + return "<Unknown>"; + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { StringBuilder s = new StringBuilder(); @@ -2343,4 +2444,3 @@ public class GpsLocationProvider implements LocationProviderInterface { // GNSS Configuration private static native void native_configuration_update(String configData); } - diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java index 96a5e0057d61..b5046056e913 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -245,6 +245,8 @@ final class DefaultPermissionGrantPolicy { if (verifierPackage != null && doesPackageSupportRuntimePermissions(verifierPackage)) { grantRuntimePermissionsLPw(verifierPackage, STORAGE_PERMISSIONS, true, userId); + grantRuntimePermissionsLPw(verifierPackage, PHONE_PERMISSIONS, false, userId); + grantRuntimePermissionsLPw(verifierPackage, SMS_PERMISSIONS, false, userId); } // SetupWizard diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 7024ec8b13b9..8c23648f0f0f 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -217,8 +217,7 @@ final class PackageDexOptimizer { @Nullable private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) throws IOException { - if ((pkg.isSystemApp() && !pkg.isUpdatedSystemApp()) || pkg.isForwardLocked() - || pkg.applicationInfo.isExternalAsec()) { + if (!pkg.canHaveOatDir()) { return null; } File codePath = new File(pkg.codePath); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d40932585ebb..b7a587b0b7d2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -13851,7 +13851,21 @@ public class PackageManagerService extends IPackageManager.Stub { // TODO(multiArch): Extend getSizeInfo to look at *all* instruction sets, not // just the primary. String[] dexCodeInstructionSets = getDexCodeInstructionSets(getAppDexInstructionSets(ps)); - int res = mInstaller.getSizeInfo(p.volumeUuid, packageName, userHandle, p.baseCodePath, + + String apkPath; + File packageDir = new File(p.codePath); + + if (packageDir.isDirectory() && p.canHaveOatDir()) { + apkPath = packageDir.getAbsolutePath(); + // If libDirRoot is inside a package dir, set it to null to avoid it being counted twice + if (libDirRoot != null && libDirRoot.startsWith(apkPath)) { + libDirRoot = null; + } + } else { + apkPath = p.baseCodePath; + } + + int res = mInstaller.getSizeInfo(p.volumeUuid, packageName, userHandle, apkPath, libDirRoot, publicSrcDir, asecPath, dexCodeInstructionSets, pStats); if (res < 0) { return false; diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java index 9095f57b175a..051b7fb17d69 100644 --- a/services/core/java/com/android/server/policy/BarController.java +++ b/services/core/java/com/android/server/policy/BarController.java @@ -258,7 +258,7 @@ public class BarController { vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; // never show transient bars in low profile } if ((vis & mTranslucentFlag) != 0 || (oldVis & mTranslucentFlag) != 0 || - ((vis | oldVis) & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) { + ((vis | oldVis) & View.SYSTEM_UI_TRANSPARENT) != 0) { mLastTranslucent = SystemClock.uptimeMillis(); } return vis; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 325196d43056..e5e468fbe7ea 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -20,8 +20,8 @@ import static android.app.ActivityManager.HOME_STACK_ID; import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerService.TAG; +import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP; -import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; import android.util.DisplayMetrics; @@ -72,6 +72,7 @@ class DisplayContent { boolean mDisplayScalingDisabled; private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Display mDisplay; + private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); Rect mBaseDisplayRect = new Rect(); Rect mContentRect = new Rect(); @@ -82,11 +83,11 @@ class DisplayContent { final boolean isDefaultDisplay; /** Window tokens that are in the process of exiting, but still on screen for animations. */ - final ArrayList<WindowToken> mExitingTokens = new ArrayList<WindowToken>(); + final ArrayList<WindowToken> mExitingTokens = new ArrayList<>(); /** Array containing all TaskStacks on this display. Array * is stored in display order with the current bottom stack at 0. */ - private final ArrayList<TaskStack> mStacks = new ArrayList<TaskStack>(); + private final ArrayList<TaskStack> mStacks = new ArrayList<>(); /** A special TaskStack with id==HOME_STACK_ID that moves to the bottom whenever any TaskStack * (except a future lockscreen TaskStack) moves to the top. */ @@ -117,6 +118,7 @@ class DisplayContent { mDisplay = display; mDisplayId = display.getDisplayId(); display.getDisplayInfo(mDisplayInfo); + display.getMetrics(mDisplayMetrics); isDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY; mService = service; } @@ -137,6 +139,10 @@ class DisplayContent { return mDisplayInfo; } + DisplayMetrics getDisplayMetrics() { + return mDisplayMetrics; + } + /** * Returns true if the specified UID has access to this display. */ @@ -174,6 +180,7 @@ class DisplayContent { void updateDisplayInfo() { mDisplay.getDisplayInfo(mDisplayInfo); + mDisplay.getMetrics(mDisplayMetrics); for (int i = mStacks.size() - 1; i >= 0; --i) { mStacks.get(i).updateDisplayInfo(null); } @@ -234,7 +241,7 @@ class DisplayContent { for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { final Task task = tasks.get(taskNdx); task.getBounds(mTmpRect); - if (mTmpRect.contains(x, y)) { + if (task.inFreeformWorkspace() && mTmpRect.contains(x, y)) { return task.mTaskId; } } @@ -247,7 +254,7 @@ class DisplayContent { * falls within. Returns -1 if the touch doesn't fall into a resizing area. */ int taskIdForControlPoint(int x, int y) { - final int delta = calculatePixelFromDp(WindowState.RESIZE_HANDLE_WIDTH_IN_DP); + final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { TaskStack stack = mStacks.get(stackNdx); if (!stack.allowTaskResize()) { @@ -275,17 +282,10 @@ class DisplayContent { return -1; } - private int calculatePixelFromDp(int dp) { - final Configuration serviceConfig = mService.mCurConfiguration; - // TODO(multidisplay): Update Dp to that of display stack is on. - final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; - return (int)(dp * density); - } - void setTouchExcludeRegion(Task focusedTask) { mTouchExcludeRegion.set(mBaseDisplayRect); WindowList windows = getWindowList(); - final int delta = calculatePixelFromDp(WindowState.RESIZE_HANDLE_WIDTH_IN_DP); + final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); for (int i = windows.size() - 1; i >= 0; --i) { final WindowState win = windows.get(i); final Task task = win.mAppToken != null ? win.getTask() : null; @@ -299,7 +299,7 @@ class DisplayContent { * (For freeform focused task, the below logic will first remove the enlarged * area, then add back the inner area.) */ - final boolean isFreeformed = win.inFreeformWorkspace(); + final boolean isFreeformed = task.inFreeformWorkspace(); if (task != focusedTask || isFreeformed) { mTmpRect.set(win.mVisibleFrame); mTmpRect.intersect(win.mVisibleInsets); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 07a850bfe17c..cc9efdbe90af 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -426,6 +426,10 @@ class Task implements DimLayer.DimLayerUser { return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers; } + boolean inFreeformWorkspace() { + return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID; + } + @Override public boolean isFullscreen() { return mFullscreen; diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index 71b83a5e32ea..7e32b84b3946 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -16,13 +16,14 @@ package com.android.server.wm; -import com.android.server.input.InputApplicationHandle; -import com.android.server.input.InputWindowHandle; -import com.android.server.wm.WindowManagerService.H; +import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; +import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; +import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static com.android.server.wm.WindowManagerService.DEBUG_TASK_POSITIONING; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import static com.android.server.wm.WindowManagerService.SHOW_TRANSACTIONS; +import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; +import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; import android.annotation.IntDef; import android.graphics.Point; @@ -32,18 +33,30 @@ import android.os.Process; import android.os.RemoteException; import android.util.DisplayMetrics; import android.util.Slog; -import android.util.TypedValue; import android.view.Display; +import android.view.DisplayInfo; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.MotionEvent; +import android.view.SurfaceControl; import android.view.WindowManager; -class TaskPositioner { +import com.android.server.input.InputApplicationHandle; +import com.android.server.input.InputWindowHandle; +import com.android.server.wm.WindowManagerService.H; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +class TaskPositioner implements DimLayer.DimLayerUser { private static final String TAG = "TaskPositioner"; + // The margin the pointer position has to be within the side of the screen to be + // considered at the side of the screen. + private static final int SIDE_MARGIN_DIP = 100; + @IntDef(flag = true, value = { CTRL_NONE, @@ -65,8 +78,15 @@ class TaskPositioner { private WindowPositionerEventReceiver mInputEventReceiver; private Display mDisplay; private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + private DimLayer mDimLayer; + @CtrlType + private int mCurrentDimSide; + private Rect mTmpRect = new Rect(); + private int mSideMargin; private int mTaskId; + private TaskStack mStack; + private boolean mResizing; private final Rect mWindowOriginalBounds = new Rect(); private final Rect mWindowDragBounds = new Rect(); private float mStartDragX; @@ -113,7 +133,8 @@ class TaskPositioner { notifyMoveLocked(newX, newY); } try { - mService.mActivityManager.resizeTask(mTaskId, mWindowDragBounds); + mService.mActivityManager.resizeTask( + mTaskId, mWindowDragBounds, true /* resizedByUser */); } catch(RemoteException e) {} } break; @@ -133,9 +154,21 @@ class TaskPositioner { } if (endDrag) { + mResizing = false; + try { + mService.mActivityManager.resizeTask( + mTaskId, mWindowDragBounds, true /* resizedByUser */); + } catch(RemoteException e) {} // Post back to WM to handle clean-ups. We still need the input // event handler for the last finishInputEvent()! mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING); + if (mCurrentDimSide != CTRL_NONE) { + final int createMode = mCurrentDimSide == CTRL_LEFT + ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT + : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT; + mService.mActivityManager.moveTaskToDockedStack( + mTaskId, createMode, true /*toTop*/); + } } handled = true; } catch (Exception e) { @@ -213,6 +246,9 @@ class TaskPositioner { Slog.d(TAG, "Pausing rotation during re-position"); } mService.pauseRotationLocked(); + + mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId()); + mSideMargin = mService.dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics); } void unregister() { @@ -238,6 +274,12 @@ class TaskPositioner { mDragApplicationHandle = null; mDisplay = null; + if (mDimLayer != null) { + mDimLayer.destroySurface(); + mDimLayer = null; + } + mCurrentDimSide = CTRL_NONE; + // Resume rotations after a drag. if (WindowManagerService.DEBUG_ORIENTATION) { Slog.d(TAG, "Resuming rotation after re-position"); @@ -245,9 +287,13 @@ class TaskPositioner { mService.resumeRotationLocked(); } + boolean isTaskResizing(final Task task) { + return mResizing && task != null && mTaskId == task.mTaskId; + } + void startDragLocked(WindowState win, boolean resize, float startX, float startY) { - if (DEBUG_TASK_POSITIONING) {Slog.d(TAG, - "startDragLocked: win=" + win + ", resize=" + resize + if (DEBUG_TASK_POSITIONING) { + Slog.d(TAG, "startDragLocked: win=" + win + ", resize=" + resize + ", {" + startX + ", " + startY + "}"); } mCtrlType = CTRL_NONE; @@ -265,10 +311,12 @@ class TaskPositioner { if (startY > visibleFrame.bottom) { mCtrlType |= CTRL_BOTTOM; } + mResizing = true; } final Task task = win.getTask(); mTaskId = task.mTaskId; + mStack = task.mStack; mStartDragX = startX; mStartDragY = startY; @@ -284,10 +332,8 @@ class TaskPositioner { // This is a resizing operation. final int deltaX = Math.round(x - mStartDragX); final int deltaY = Math.round(y - mStartDragY); - // TODO: fix the min sizes when we have mininum width/height support, - // use hard-coded min sizes for now. - final int minSizeX = (int)(dipToPx(96)); - final int minSizeY = (int)(dipToPx(64)); + final int minSizeX = mService.dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics); + final int minSizeY = mService.dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics); int left = mWindowOriginalBounds.left; int top = mWindowOriginalBounds.top; int right = mWindowOriginalBounds.right; @@ -308,9 +354,75 @@ class TaskPositioner { } else { // This is a moving operation. mWindowDragBounds.set(mWindowOriginalBounds); - mWindowDragBounds.offset(Math.round(x - mStartDragX), - Math.round(y - mStartDragY)); + mWindowDragBounds.offset(Math.round(x - mStartDragX), Math.round(y - mStartDragY)); + updateDimLayerVisibility((int) x); + } + } + + private void updateDimLayerVisibility(int x) { + @CtrlType + int dimSide = getDimSide(x); + if (dimSide == mCurrentDimSide) { + return; + } + + mCurrentDimSide = dimSide; + + if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility"); + SurfaceControl.openTransaction(); + if (mCurrentDimSide == CTRL_NONE) { + mDimLayer.hide(); + } else { + showDimLayer(); + } + SurfaceControl.closeTransaction(); + } + + /** + * Returns the side of the screen the dim layer should be shown. + * @param x horizontal coordinate used to determine if the dim layer should be shown + * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the + * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer + * shouldn't be shown. + */ + private int getDimSide(int x) { + if (mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID + || !mStack.isFullscreen() + || mService.mCurConfiguration.orientation != ORIENTATION_LANDSCAPE) { + return CTRL_NONE; + } + + mStack.getBounds(mTmpRect); + if (x - mSideMargin <= mTmpRect.left) { + return CTRL_LEFT; } + if (x + mSideMargin >= mTmpRect.right) { + return CTRL_RIGHT; + } + + return CTRL_NONE; + } + + private void showDimLayer() { + mStack.getBounds(mTmpRect); + if (mCurrentDimSide == CTRL_LEFT) { + mTmpRect.right = mTmpRect.centerX(); + } else if (mCurrentDimSide == CTRL_RIGHT) { + mTmpRect.left = mTmpRect.centerX(); + } + + mDimLayer.setBounds(mTmpRect); + mDimLayer.show(getDragLayerLocked(), 0.5f, 0); + } + + @Override /** {@link DimLayer.DimLayerUser} */ + public boolean isFullscreen() { + return false; + } + + @Override /** {@link DimLayer.DimLayerUser} */ + public DisplayInfo getDisplayInfo() { + return mStack.getDisplayInfo(); } private int getDragLayerLocked() { @@ -318,8 +430,4 @@ class TaskPositioner { * WindowManagerService.TYPE_LAYER_MULTIPLIER + WindowManagerService.TYPE_LAYER_OFFSET; } - - private float dipToPx(float dip) { - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, mDisplayMetrics); - } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index ae3bb9b20b04..1e6fab600594 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -353,11 +353,13 @@ public class TaskStack implements DimLayer.DimLayerUser { private static void getInitialDockedStackBounds( Rect displayRect, Rect outBounds, int stackId) { // Docked stack start off occupying half the screen space. - // TODO(multi-window): Need to support the selecting which half of the screen the - // docked stack uses for snapping windows to the edge of the screen. final boolean splitHorizontally = displayRect.width() > displayRect.height(); + final boolean topOrLeftCreateMode = + WindowManagerService.sDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; + final boolean placeTopOrLeft = (stackId == DOCKED_STACK_ID && topOrLeftCreateMode) + || (stackId != DOCKED_STACK_ID && !topOrLeftCreateMode); outBounds.set(displayRect); - if (stackId == DOCKED_STACK_ID) { + if (placeTopOrLeft) { if (splitHorizontally) { outBounds.right = displayRect.centerX(); } else { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index fc23fd1b2f9f..1a946b2c5d17 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -736,7 +736,8 @@ class WallpaperController { insertionIndex = windows.indexOf(wallpaperTarget); } } - if (DEBUG_WALLPAPER_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, + if (DEBUG_WALLPAPER_LIGHT || DEBUG_WINDOW_MOVEMENT + || (DEBUG_ADD_REMOVE && oldIndex != insertionIndex)) Slog.v(TAG, "Moving wallpaper " + wallpaper + " from " + oldIndex + " to " + insertionIndex); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 69440b883d00..554587d212f5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -16,6 +16,34 @@ package com.android.server.wm; +import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; +import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; +import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; +import static android.view.WindowManager.LayoutParams.TYPE_DREAM; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; +import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + +import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH; + import android.Manifest; import android.animation.ValueAnimator; import android.app.ActivityManagerNative; @@ -110,9 +138,7 @@ import android.view.WindowManagerPolicy; import android.view.WindowManagerPolicy.PointerEventListener; import android.view.animation.Animation; -import static com.android.server.wm.WindowState.BOUNDS_FOR_TOUCH; import com.android.internal.app.IAssistScreenshotReceiver; -import com.android.internal.app.IBatteryStats; import com.android.internal.util.FastPrintWriter; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; @@ -149,31 +175,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; -import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_SECURE; -import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; -import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; -import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; -import static android.view.WindowManager.LayoutParams.TYPE_DREAM; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; -import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; - /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { @@ -458,6 +459,8 @@ public class WindowManagerService extends IWindowManager.Stub private boolean mKeyguardWaitingForActivityDrawn; + static int sDockedStackCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; + class RotationWatcher { IRotationWatcher watcher; IBinder.DeathRecipient deathRecipient; @@ -904,7 +907,6 @@ public class WindowManagerService extends IWindowManager.Stub SurfaceControl.closeTransaction(); } - updateCircularDisplayMaskIfNeeded(); showEmulatorDisplayOverlayIfNeeded(); } @@ -4448,6 +4450,12 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void setDockedStackCreateMode(int mode) { + synchronized (mWindowMap) { + sDockedStackCreateMode = mode; + } + } + /** * Create a new TaskStack and place it on a DisplayContent. * @param stackId The unique identifier of the new stack. @@ -5298,7 +5306,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void updateCircularDisplayMaskIfNeeded() { + private void updateCircularDisplayMaskIfNeeded() { // we're fullscreen and not hosted in an ActivityView if (mContext.getResources().getConfiguration().isScreenRound() && mContext.getResources().getBoolean( @@ -7090,6 +7098,8 @@ public class WindowManagerService extends IWindowManager.Stub mActivityManager.updateConfiguration(null); } catch (RemoteException e) { } + + updateCircularDisplayMaskIfNeeded(); } private void displayReady(int displayId) { @@ -9853,6 +9863,10 @@ public class WindowManagerService extends IWindowManager.Stub } } + static int dipToPixel(int dip, DisplayMetrics displayMetrics) { + return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics); + } + private final class LocalService extends WindowManagerInternal { @Override public void requestTraversalFromDisplayManager() { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 789354db1739..1deccb477f55 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; @@ -81,12 +80,14 @@ final class WindowState implements WindowManagerPolicy.WindowState { static final String TAG = "WindowState"; // The minimal size of a window within the usable area of the freeform stack. - private static final int MINIMUM_VISIBLE_WIDTH_IN_DP = 48; - private static final int MINIMUM_VISIBLE_HEIGHT_IN_DP = 32; + // TODO(multi-window): fix the min sizes when we have mininum width/height support, + // use hard-coded min sizes for now. + static final int MINIMUM_VISIBLE_WIDTH_IN_DP = 48; + static final int MINIMUM_VISIBLE_HEIGHT_IN_DP = 32; // The thickness of a window resize handle outside the window bounds on the free form workspace // to capture touch events in that area. - static final int RESIZE_HANDLE_WIDTH_IN_DP = 10; + static final int RESIZE_HANDLE_WIDTH_IN_DP = 30; static final boolean BOUNDS_FOR_TOUCH = true; @@ -557,7 +558,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { final Task task = mAppToken != null ? getTask() : null; final boolean nonFullscreenTask = task != null && !task.isFullscreen(); - final boolean freeformWorkspace = inFreeformWorkspace(); + final boolean freeformWorkspace = task != null && task.inFreeformWorkspace(); if (nonFullscreenTask) { task.getBounds(mContainingFrame); final WindowState imeWin = mService.mInputMethodWindow; @@ -689,8 +690,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { // into a usable area.. final int height = Math.min(mFrame.height(), mContentFrame.height()); final int width = Math.min(mContentFrame.width(), mFrame.width()); - final int minVisibleHeight = calculatePixelFromDp(MINIMUM_VISIBLE_HEIGHT_IN_DP); - final int minVisibleWidth = calculatePixelFromDp(MINIMUM_VISIBLE_WIDTH_IN_DP); + final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics(); + final int minVisibleHeight = + mService.dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics); + final int minVisibleWidth = + mService.dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics); final int top = Math.max(mContentFrame.top, Math.min(mFrame.top, mContentFrame.bottom - minVisibleHeight)); final int left = Math.max(mContentFrame.left + minVisibleWidth - width, @@ -897,6 +901,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { return stack == null ? mDisplayContent : stack.getDisplayContent(); } + public DisplayInfo getDisplayInfo() { + final DisplayContent displayContent = getDisplayContent(); + return displayContent != null ? displayContent.getDisplayInfo() : null; + } + public int getDisplayId() { final DisplayContent displayContent = getDisplayContent(); if (displayContent == null) { @@ -935,8 +944,10 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (task != null) { task.getBounds(bounds); if (forTouch == BOUNDS_FOR_TOUCH) { - if (inFreeformWorkspace()) { - final int delta = calculatePixelFromDp(RESIZE_HANDLE_WIDTH_IN_DP); + if (task.inFreeformWorkspace()) { + final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics(); + final int delta = + mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, displayMetrics); bounds.inset(-delta, -delta); } } @@ -1668,16 +1679,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { } boolean inFreeformWorkspace() { - final Task task = getTask(); - return task != null && task.mStack != null && - task.mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID; + final Task task = mAppToken != null ? getTask() : null; + return task != null && task.inFreeformWorkspace(); } - private int calculatePixelFromDp(int dp) { - final Configuration serviceConfig = mService.mCurConfiguration; - // TODO(multidisplay): Update Dp to that of display stack is on. - final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; - return (int)(dp * density); + boolean isDragResizing() { + final Task task = mAppToken != null ? getTask() : null; + return mService.mTaskPositioner != null && mService.mTaskPositioner.isTaskResizing(task); } void dump(PrintWriter pw, String prefix, boolean dumpAll) { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index bf1ab8fab7d0..dfc9784249a9 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -115,6 +115,7 @@ class WindowStateAnimator { Rect mClipRect = new Rect(); Rect mTmpClipRect = new Rect(); Rect mLastClipRect = new Rect(); + Rect mTmpStackBounds = new Rect(); // Used to save animation distances between the time they are calculated and when they are // used. @@ -168,6 +169,11 @@ class WindowStateAnimator { /** Set when the window has been shown in the screen the first time. */ static final int HAS_DRAWN = 4; + // Surface flinger doesn't support crop rectangles where width or height is non-positive. + // However, we need to somehow handle the situation where the cropping would completely hide + // the window. We achieve this by explicitly hiding the surface and not letting it be shown. + private boolean mHiddenForCrop; + String drawStateToString() { switch (mDrawState) { case NO_SURFACE: return "NO_SURFACE"; @@ -798,8 +804,17 @@ class WindowStateAnimator { width = w.mRequestedWidth; height = w.mRequestedHeight; } else { - width = w.mCompatFrame.width(); - height = w.mCompatFrame.height(); + // When we're doing a drag-resizing, request a surface that's fullscreen size, + // so that we don't need to reallocate during the process. This also prevents + // buffer drops due to size mismatch. + final DisplayInfo displayInfo = w.getDisplayInfo(); + if (displayInfo != null && w.isDragResizing()) { + width = displayInfo.logicalWidth; + height = displayInfo.logicalHeight; + } else { + width = w.mCompatFrame.width(); + height = w.mCompatFrame.height(); + } } // Something is wrong and SurfaceFlinger will not like this, @@ -1309,9 +1324,15 @@ class WindowStateAnimator { final boolean fullscreen = w.isFullscreen(displayInfo.appWidth, displayInfo.appHeight); final Rect clipRect = mTmpClipRect; - // We use the clip rect as provided by the tranformation for non-fullscreen windows to - // avoid premature clipping with the system decor rect. - clipRect.set((mHasClipRect && !fullscreen) ? mClipRect : w.mSystemDecorRect); + if (w.isDragResizing()) { + // When we're doing a drag-resizing, the surface is set up to cover full screen. + // Set the clip rect to be the same size so that we don't get any scaling. + clipRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); + } else { + // We use the clip rect as provided by the tranformation for non-fullscreen windows to + // avoid premature clipping with the system decor rect. + clipRect.set((mHasClipRect && !fullscreen) ? mClipRect : w.mSystemDecorRect); + } // Expand the clip rect for surface insets. final WindowManager.LayoutParams attrs = w.mAttrs; @@ -1331,12 +1352,20 @@ class WindowStateAnimator { // so we need to translate to match the actual surface coordinates. clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top); + adjustCropToStackBounds(w, clipRect); + if (!clipRect.equals(mLastClipRect)) { mLastClipRect.set(clipRect); try { if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, "CROP " + clipRect.toShortString(), null); - mSurfaceControl.setWindowCrop(clipRect); + if (clipRect.width() > 0 && clipRect.height() > 0) { + mSurfaceControl.setWindowCrop(clipRect); + mHiddenForCrop = false; + } else { + hide(); + mHiddenForCrop = true; + } } catch (RuntimeException e) { Slog.w(TAG, "Error setting crop surface of " + w + " crop=" + clipRect.toShortString(), e); @@ -1347,6 +1376,25 @@ class WindowStateAnimator { } } + private void adjustCropToStackBounds(WindowState w, Rect clipRect) { + if (w.getAttrs().type == LayoutParams.TYPE_BASE_APPLICATION) { + TaskStack stack = w.getTask().mStack; + stack.getBounds(mTmpStackBounds); + final int surfaceX = (int) mSurfaceX; + final int surfaceY = (int) mSurfaceY; + // We need to do some acrobatics with surface position, because their clip region is + // relative to the inside of the surface, but the stack bounds aren't. + clipRect.left = Math.max(0, + Math.max(mTmpStackBounds.left, surfaceX + clipRect.left) - surfaceX); + clipRect.top = Math.max(0, + Math.max(mTmpStackBounds.top, surfaceY + clipRect.top) - surfaceY); + clipRect.right = Math.max(0, + Math.min(mTmpStackBounds.right, surfaceX + clipRect.right) - surfaceX); + clipRect.bottom = Math.max(0, + Math.min(mTmpStackBounds.bottom, surfaceY + clipRect.bottom) - surfaceY); + } + } + void setSurfaceBoundariesLocked(final boolean recoveringMemory) { final WindowState w = mWin; @@ -1358,8 +1406,17 @@ class WindowStateAnimator { width = w.mRequestedWidth; height = w.mRequestedHeight; } else { - width = w.mCompatFrame.width(); - height = w.mCompatFrame.height(); + // When we're doing a drag-resizing, request a surface that's fullscreen size, + // so that we don't need to reallocate during the process. This also prevents + // buffer drops due to size mismatch. + final DisplayInfo displayInfo = w.getDisplayInfo(); + if (displayInfo != null && w.isDragResizing()) { + width = displayInfo.logicalWidth; + height = displayInfo.logicalHeight; + } else { + width = w.mCompatFrame.width(); + height = w.mCompatFrame.height(); + } } // Something is wrong and SurfaceFlinger will not like this, @@ -1517,7 +1574,7 @@ class WindowStateAnimator { mDsDx * w.mHScale, mDtDx * w.mVScale, mDsDy * w.mHScale, mDtDy * w.mVScale); - if (mLastHidden && mDrawState == HAS_DRAWN) { + if (mLastHidden && mDrawState == HAS_DRAWN && !mHiddenForCrop) { if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, "SHOW (performLayout)", null); if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 52efa6803499..86da94f7d2be 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -712,7 +712,12 @@ class WindowSurfacePlacer { } } } - + /* + * Updates the shown frame before we set up the surface. This is needed because + * the resizing could change the top-left position (in addition to size) of the + * window. setSurfaceBoundariesLocked uses mShownFrame to position the surface. + */ + winAnimator.computeShownFrameLocked(); winAnimator.setSurfaceBoundariesLocked(recoveringMemory); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index aafaf8373cf8..93dc6cbcfe99 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1327,4 +1327,4 @@ public final class SystemServer { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name); Slog.i(TAG, name); } -} +}
\ No newline at end of file diff --git a/services/usage/java/com/android/server/usage/UnixCalendar.java b/services/usage/java/com/android/server/usage/UnixCalendar.java index ce06a91bd148..db7b42dca8fb 100644 --- a/services/usage/java/com/android/server/usage/UnixCalendar.java +++ b/services/usage/java/com/android/server/usage/UnixCalendar.java @@ -15,40 +15,22 @@ */ package com.android.server.usage; -import android.app.usage.UsageStatsManager; - /** * A handy calendar object that knows nothing of Locale's or TimeZones. This simplifies * interval book-keeping. It is *NOT* meant to be used as a user-facing calendar, as it has * no concept of Locale or TimeZone. */ public class UnixCalendar { - private static final long DAY_IN_MILLIS = 24 * 60 * 60 * 1000; - private static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS; - private static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS; - private static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS; + public static final long DAY_IN_MILLIS = 24 * 60 * 60 * 1000; + public static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS; + public static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS; + public static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS; private long mTime; public UnixCalendar(long time) { mTime = time; } - public void truncateToDay() { - mTime -= mTime % DAY_IN_MILLIS; - } - - public void truncateToWeek() { - mTime -= mTime % WEEK_IN_MILLIS; - } - - public void truncateToMonth() { - mTime -= mTime % MONTH_IN_MILLIS; - } - - public void truncateToYear() { - mTime -= mTime % YEAR_IN_MILLIS; - } - public void addDays(int val) { mTime += val * DAY_IN_MILLIS; } @@ -72,28 +54,4 @@ public class UnixCalendar { public long getTimeInMillis() { return mTime; } - - public static void truncateTo(UnixCalendar calendar, int intervalType) { - switch (intervalType) { - case UsageStatsManager.INTERVAL_YEARLY: - calendar.truncateToYear(); - break; - - case UsageStatsManager.INTERVAL_MONTHLY: - calendar.truncateToMonth(); - break; - - case UsageStatsManager.INTERVAL_WEEKLY: - calendar.truncateToWeek(); - break; - - case UsageStatsManager.INTERVAL_DAILY: - calendar.truncateToDay(); - break; - - default: - throw new UnsupportedOperationException("Can't truncate date to interval " + - intervalType); - } - } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index f8ae03f1107c..0ca4bd8c6c04 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -350,23 +350,6 @@ class UsageStatsDatabase { } /** - * Get the time at which the latest stats begin for this interval type. - */ - public long getLatestUsageStatsBeginTime(int intervalType) { - synchronized (mLock) { - if (intervalType < 0 || intervalType >= mIntervalDirs.length) { - throw new IllegalArgumentException("Bad interval type " + intervalType); - } - - final int statsFileCount = mSortedStatFiles[intervalType].size(); - if (statsFileCount > 0) { - return mSortedStatFiles[intervalType].keyAt(statsFileCount - 1); - } - return -1; - } - } - - /** * Figures out what to extract from the given IntervalStats object. */ interface StatCombiner<T> { diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index b07b8153279d..5188e5fd1f7d 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -65,6 +65,11 @@ class UserUsageStatsService { private final String mLogPrefix; private final int mUserId; + private static final long[] INTERVAL_LENGTH = new long[] { + UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS, + UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS + }; + interface StatsUpdatedListener { void onStatsUpdated(); } @@ -104,18 +109,12 @@ class UserUsageStatsService { // By calling loadActiveStats, we will // generate new stats for each bucket. - loadActiveStats(currentTimeMillis,/*force=*/ false, /*resetBeginIdleTime=*/ false); + loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false); } else { // Set up the expiry date to be one day from the latest daily stat. // This may actually be today and we will rollover on the first event // that is reported. - mDailyExpiryDate.setTimeInMillis( - mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime); - mDailyExpiryDate.addDays(1); - mDailyExpiryDate.truncateToDay(); - Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + - sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + - "(" + mDailyExpiryDate.getTimeInMillis() + ")"); + updateRolloverDeadline(); } // Now close off any events that were open at the time this was saved. @@ -170,7 +169,7 @@ class UserUsageStatsService { void onTimeChanged(long oldTime, long newTime, boolean resetBeginIdleTime) { persistActiveStats(); mDatabase.onTimeChanged(newTime - oldTime); - loadActiveStats(newTime, /* force= */ true, resetBeginIdleTime); + loadActiveStats(newTime, resetBeginIdleTime); } void reportEvent(UsageEvents.Event event, long deviceUsageTime) { @@ -237,7 +236,7 @@ class UserUsageStatsService { new StatCombiner<UsageStats>() { @Override public void combine(IntervalStats stats, boolean mutable, - List<UsageStats> accResult) { + List<UsageStats> accResult) { if (!mutable) { accResult.addAll(stats.packageStats.values()); return; @@ -254,7 +253,7 @@ class UserUsageStatsService { new StatCombiner<ConfigurationStats>() { @Override public void combine(IntervalStats stats, boolean mutable, - List<ConfigurationStats> accResult) { + List<ConfigurationStats> accResult) { if (!mutable) { accResult.addAll(stats.configurations.values()); return; @@ -448,7 +447,7 @@ class UserUsageStatsService { persistActiveStats(); mDatabase.prune(currentTimeMillis); - loadActiveStats(currentTimeMillis, /*force=*/ false, /*resetBeginIdleTime=*/ false); + loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false); final int continueCount = continuePreviousDay.size(); for (int i = 0; i < continueCount; i++) { @@ -474,45 +473,28 @@ class UserUsageStatsService { } } - /** - * @param force To force all in-memory stats to be reloaded. - */ - private void loadActiveStats(final long currentTimeMillis, boolean force, - boolean resetBeginIdleTime) { - final UnixCalendar tempCal = mDailyExpiryDate; + private void loadActiveStats(final long currentTimeMillis, boolean resetBeginIdleTime) { for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) { - tempCal.setTimeInMillis(currentTimeMillis); - UnixCalendar.truncateTo(tempCal, intervalType); - - if (!force && mCurrentStats[intervalType] != null && - mCurrentStats[intervalType].beginTime == tempCal.getTimeInMillis()) { - // These are the same, no need to load them (in memory stats are always newer - // than persisted stats). - continue; - } - - final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(intervalType); - if (lastBeginTime >= tempCal.getTimeInMillis()) { + final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType); + if (stats != null && currentTimeMillis - 500 >= stats.endTime && + currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) { if (DEBUG) { Slog.d(TAG, mLogPrefix + "Loading existing stats @ " + - sDateFormat.format(lastBeginTime) + "(" + lastBeginTime + + sDateFormat.format(stats.beginTime) + "(" + stats.beginTime + ") for interval " + intervalType); } - mCurrentStats[intervalType] = mDatabase.getLatestUsageStats(intervalType); + mCurrentStats[intervalType] = stats; } else { - mCurrentStats[intervalType] = null; - } - - if (mCurrentStats[intervalType] == null) { + // No good fit remains. if (DEBUG) { Slog.d(TAG, "Creating new stats @ " + - sDateFormat.format(tempCal.getTimeInMillis()) + "(" + - tempCal.getTimeInMillis() + ") for interval " + intervalType); - + sDateFormat.format(currentTimeMillis) + "(" + + currentTimeMillis + ") for interval " + intervalType); } + mCurrentStats[intervalType] = new IntervalStats(); - mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis(); - mCurrentStats[intervalType].endTime = currentTimeMillis; + mCurrentStats[intervalType].beginTime = currentTimeMillis; + mCurrentStats[intervalType].endTime = currentTimeMillis + 1; } if (resetBeginIdleTime) { @@ -522,12 +504,16 @@ class UserUsageStatsService { } } mStatsChanged = false; - mDailyExpiryDate.setTimeInMillis(currentTimeMillis); + updateRolloverDeadline(); + } + + private void updateRolloverDeadline() { + mDailyExpiryDate.setTimeInMillis( + mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime); mDailyExpiryDate.addDays(1); - mDailyExpiryDate.truncateToDay(); Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" + - tempCal.getTimeInMillis() + ")"); + mDailyExpiryDate.getTimeInMillis() + ")"); } // diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index b868832b087e..e57964a96182 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -326,6 +326,14 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = "carrier_force_disable_etws_cmas_test_bool"; + /** + * The default flag specifying whether "Turn on Notifications" option will be always shown in + * Settings->More->Emergency broadcasts menu regardless developer options is turned on or not. + * @hide + */ + public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = + "always_show_emergency_alert_onoff_bool"; + /* The following 3 fields are related to carrier visual voicemail. */ /** @@ -532,6 +540,7 @@ public class CarrierConfigManager { sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING, ""); sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING, ""); sDefaults.putBoolean(KEY_CSP_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false); sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null); sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null); diff --git a/tools/aapt2/Logger.h b/tools/aapt2/Logger.h index 1d437ebe6492..eed58b83ffc4 100644 --- a/tools/aapt2/Logger.h +++ b/tools/aapt2/Logger.h @@ -18,11 +18,11 @@ #define AAPT_LOGGER_H #include "Source.h" +#include "StringPiece.h" #include <memory> #include <ostream> #include <string> -#include <utils/String8.h> namespace aapt { @@ -71,11 +71,6 @@ private: Source mSource; }; -inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) { - android::String8 utf8(str.data(), str.size()); - return out.write(utf8.string(), utf8.size()); -} - } // namespace aapt #endif // AAPT_LOGGER_H diff --git a/tools/aapt2/StringPiece.h b/tools/aapt2/StringPiece.h index e2a1597caeda..8cbdeae5e892 100644 --- a/tools/aapt2/StringPiece.h +++ b/tools/aapt2/StringPiece.h @@ -229,4 +229,9 @@ inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<ch } // namespace aapt +inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) { + android::String8 utf8(str.data(), str.size()); + return out.write(utf8.string(), utf8.size()); +} + #endif // AAPT_STRING_PIECE_H diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/Util.cpp index 03ecd1aca310..ca352e0e9b61 100644 --- a/tools/aapt2/Util.cpp +++ b/tools/aapt2/Util.cpp @@ -175,7 +175,51 @@ StringBuilder& StringBuilder::append(const StringPiece16& str) { const char16_t* start = str.begin(); const char16_t* current = start; while (current != end) { - if (*current == u'"') { + if (mLastCharWasEscape) { + switch (*current) { + case u't': + mStr += u'\t'; + break; + case u'n': + mStr += u'\n'; + break; + case u'#': + mStr += u'#'; + break; + case u'@': + mStr += u'@'; + break; + case u'?': + mStr += u'?'; + break; + case u'"': + mStr += u'"'; + break; + case u'\'': + mStr += u'\''; + break; + case u'\\': + mStr += u'\\'; + break; + case u'u': { + current++; + Maybe<char16_t> c = parseUnicodeCodepoint(¤t, end); + if (!c) { + mError = "invalid unicode escape sequence"; + return *this; + } + mStr += c.value(); + current -= 1; + break; + } + + default: + // Ignore. + break; + } + mLastCharWasEscape = false; + start = current + 1; + } else if (*current == u'"') { if (!mQuote && mTrailingSpace) { // We found an opening quote, and we have // trailing space, so we should append that @@ -208,52 +252,7 @@ StringBuilder& StringBuilder::append(const StringPiece16& str) { } mStr.append(start, current - start); start = current + 1; - - current++; - if (current != end) { - switch (*current) { - case u't': - mStr += u'\t'; - break; - case u'n': - mStr += u'\n'; - break; - case u'#': - mStr += u'#'; - break; - case u'@': - mStr += u'@'; - break; - case u'?': - mStr += u'?'; - break; - case u'"': - mStr += u'"'; - break; - case u'\'': - mStr += u'\''; - break; - case u'\\': - mStr += u'\\'; - break; - case u'u': { - current++; - Maybe<char16_t> c = parseUnicodeCodepoint(¤t, end); - if (!c) { - mError = "invalid unicode escape sequence"; - return *this; - } - mStr += c.value(); - current -= 1; - break; - } - - default: - // Ignore. - break; - } - start = current + 1; - } + mLastCharWasEscape = true; } else if (!mQuote) { // This is not quoted text, so look for whitespace. if (isspace16(*current)) { diff --git a/tools/aapt2/Util.h b/tools/aapt2/Util.h index 9cdb152bf41f..7ec6b030fd85 100644 --- a/tools/aapt2/Util.h +++ b/tools/aapt2/Util.h @@ -162,6 +162,7 @@ private: std::u16string mStr; bool mQuote = false; bool mTrailingSpace = false; + bool mLastCharWasEscape = false; std::string mError; }; diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp index 0b08d240cad3..92f2a1c0f1d1 100644 --- a/tools/aapt2/Util_test.cpp +++ b/tools/aapt2/Util_test.cpp @@ -38,6 +38,13 @@ TEST(UtilTest, StringStartsWith) { EXPECT_TRUE(util::stringStartsWith<char>("hello.xml", "he")); } +TEST(UtilTest, StringBuilderSplitEscapeSequence) { + EXPECT_EQ(StringPiece16(u"this is a new\nline."), + util::StringBuilder().append(u"this is a new\\") + .append(u"nline.") + .str()); +} + TEST(UtilTest, StringBuilderWhitespaceRemoval) { EXPECT_EQ(StringPiece16(u"hey guys this is so cool"), util::StringBuilder().append(u" hey guys ") |