summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/UserManagerInternal.java12
-rw-r--r--core/java/android/provider/CallLog.java33
-rw-r--r--core/java/android/security/net/config/NetworkSecurityTrustManager.java4
-rw-r--r--core/java/android/security/net/config/RootTrustManager.java12
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java45
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl15
-rw-r--r--core/java/com/android/internal/view/InputMethodClient.java43
-rw-r--r--libs/hwui/Android.mk5
-rw-r--r--libs/hwui/BakedOpDispatcher.cpp264
-rw-r--r--libs/hwui/BakedOpDispatcher.h44
-rw-r--r--libs/hwui/BakedOpRenderer.cpp238
-rw-r--r--libs/hwui/BakedOpRenderer.h15
-rw-r--r--libs/hwui/DisplayList.h2
-rw-r--r--libs/hwui/DisplayListCanvas.h1
-rw-r--r--libs/hwui/DisplayListOp.h14
-rw-r--r--libs/hwui/FontRenderer.cpp3
-rw-r--r--libs/hwui/OpReorderer.cpp69
-rw-r--r--libs/hwui/OpReorderer.h13
-rw-r--r--libs/hwui/RecordedOp.h12
-rw-r--r--libs/hwui/RecordingCanvas.h20
-rw-r--r--libs/hwui/RenderNode.cpp44
-rw-r--r--libs/hwui/RenderNode.h13
-rw-r--r--libs/hwui/microbench/OpReordererBench.cpp50
-rw-r--r--libs/hwui/renderthread/CanvasContext.h1
-rw-r--r--libs/hwui/tests/scenes/OvalAnimation.cpp7
-rw-r--r--libs/hwui/tests/scenes/PartialDamageAnimation.cpp2
-rw-r--r--libs/hwui/tests/scenes/RectGridAnimation.cpp2
-rw-r--r--libs/hwui/tests/scenes/SaveLayerAnimation.cpp2
-rw-r--r--libs/hwui/unit_tests/LayerUpdateQueueTests.cpp2
-rw-r--r--libs/hwui/unit_tests/OpReordererTests.cpp160
-rw-r--r--libs/hwui/unit_tests/RecordingCanvasTests.cpp89
-rw-r--r--libs/hwui/utils/TestUtils.cpp78
-rw-r--r--libs/hwui/utils/TestUtils.h27
-rw-r--r--packages/Shell/Android.mk4
-rw-r--r--packages/Shell/AndroidManifest.xml6
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java284
-rw-r--r--packages/Shell/src/com/android/shell/BugreportReceiver.java253
-rw-r--r--packages/Shell/tests/Android.mk20
-rw-r--r--packages/Shell/tests/AndroidManifest.xml43
-rw-r--r--packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java126
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java242
-rw-r--r--packages/Shell/tests/src/com/android/shell/UiBot.java130
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java45
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java59
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java54
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java24
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfaceController.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfacePlacer.java20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java72
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java72
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java2
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java2
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java14
54 files changed, 1962 insertions, 878 deletions
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 26c7a1e2d0ec..898b6cffa9d4 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -69,4 +69,16 @@ public abstract class UserManagerInternal {
/** Remove a {@link UserRestrictionsListener}. */
public abstract void removeUserRestrictionsListener(UserRestrictionsListener listener);
+
+ /**
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
+ * whether the device is managed by device owner.
+ */
+ public abstract void setDeviceManaged(boolean isManaged);
+
+ /**
+ * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
+ * whether the user is managed by profile owner.
+ */
+ public abstract void setUserManaged(int userId, boolean isManaged);
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 4b63c36b8aec..1d4d57278a57 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -413,6 +413,14 @@ public class CallLog {
public static final String POST_DIAL_DIGITS = "post_dial_digits";
/**
+ * Indicates that the entry will be copied from primary user to other users.
+ * <P>Type: INTEGER</P>
+ *
+ * @hide
+ */
+ public static final String ADD_FOR_ALL_USERS = "add_for_all_users";
+
+ /**
* If a successful call is made that is longer than this duration, update the phone number
* in the ContactsProvider with the normalized version of the number, based on the user's
* current country code.
@@ -444,7 +452,7 @@ public class CallLog {
int presentation, int callType, int features, PhoneAccountHandle accountHandle,
long start, int duration, Long dataUsage) {
return addCall(ci, context, number, "", presentation, callType, features, accountHandle,
- start, duration, dataUsage, false, false);
+ start, duration, dataUsage, false, null, false);
}
@@ -467,7 +475,9 @@ public class CallLog {
* the call.
* @param addForAllUsers If true, the call is added to the call log of all currently
* running users. The caller must have the MANAGE_USERS permission if this is true.
- *
+ * @param userToBeInsertedTo {@link UserHandle} of user that the call is going to be
+ * inserted to. null if it is inserted to the current user. The
+ * value is ignored if @{link addForAllUsers} is true.
* @result The URI of the call log entry belonging to the user that made or received this
* call.
* {@hide}
@@ -475,9 +485,10 @@ public class CallLog {
public static Uri addCall(CallerInfo ci, Context context, String number,
String postDialDigits, int presentation, int callType, int features,
PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
- boolean addForAllUsers) {
+ boolean addForAllUsers, UserHandle userToBeInsertedTo) {
return addCall(ci, context, number, postDialDigits, presentation, callType, features,
- accountHandle, start, duration, dataUsage, addForAllUsers, false);
+ accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo,
+ false);
}
/**
@@ -501,6 +512,9 @@ public class CallLog {
* the call.
* @param addForAllUsers If true, the call is added to the call log of all currently
* running users. The caller must have the MANAGE_USERS permission if this is true.
+ * @param userToBeInsertedTo {@link UserHandle} of user that the call is going to be
+ * inserted to. null if it is inserted to the current user. The
+ * value is ignored if @{link addForAllUsers} is true.
* @param is_read Flag to show if the missed call log has been read by the user or not.
* Used for call log restore of missed calls.
*
@@ -511,7 +525,7 @@ public class CallLog {
public static Uri addCall(CallerInfo ci, Context context, String number,
String postDialDigits, int presentation, int callType, int features,
PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage,
- boolean addForAllUsers, boolean is_read) {
+ boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean is_read) {
final ContentResolver resolver = context.getContentResolver();
int numberPresentation = PRESENTATION_ALLOWED;
@@ -575,6 +589,7 @@ public class CallLog {
values.put(PHONE_ACCOUNT_ID, accountId);
values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
values.put(NEW, Integer.valueOf(1));
+ values.put(ADD_FOR_ALL_USERS, addForAllUsers ? 1 : 0);
if (callType == MISSED_TYPE) {
values.put(IS_READ, Integer.valueOf(is_read ? 1 : 0));
@@ -650,9 +665,13 @@ public class CallLog {
}
}
} else {
- result = addEntryAndRemoveExpiredEntries(context, CONTENT_URI, values);
+ Uri uri = CONTENT_URI;
+ if (userToBeInsertedTo != null) {
+ uri = ContentProvider
+ .maybeAddUserId(CONTENT_URI, userToBeInsertedTo.getIdentifier());
+ }
+ result = addEntryAndRemoveExpiredEntries(context, uri, values);
}
-
return result;
}
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index 7f5b3ca27bf4..2b860fac45c1 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -65,7 +65,7 @@ public class NetworkSecurityTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
- throw new CertificateException("Client authentication not supported");
+ mDelegate.checkClientTrusted(chain, authType);
}
@Override
@@ -149,6 +149,6 @@ public class NetworkSecurityTrustManager implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[0];
+ return mDelegate.getAcceptedIssuers();
}
}
diff --git a/core/java/android/security/net/config/RootTrustManager.java b/core/java/android/security/net/config/RootTrustManager.java
index b87bf1fe0695..e307ad00275e 100644
--- a/core/java/android/security/net/config/RootTrustManager.java
+++ b/core/java/android/security/net/config/RootTrustManager.java
@@ -35,7 +35,6 @@ import javax.net.ssl.X509TrustManager;
* @hide */
public class RootTrustManager implements X509TrustManager {
private final ApplicationConfig mConfig;
- private static final X509Certificate[] EMPTY_ISSUERS = new X509Certificate[0];
public RootTrustManager(ApplicationConfig config) {
if (config == null) {
@@ -47,7 +46,10 @@ public class RootTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
- throw new CertificateException("Client authentication not supported");
+ // Use the default configuration for all client authentication. Domain specific configs are
+ // only for use in checking server trust not client trust.
+ NetworkSecurityConfig config = mConfig.getConfigForHostname("");
+ config.getTrustManager().checkClientTrusted(chain, authType);
}
@Override
@@ -84,6 +86,10 @@ public class RootTrustManager implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
- return EMPTY_ISSUERS;
+ // getAcceptedIssuers is meant to be used to determine which trust anchors the server will
+ // accept when verifying clients. Domain specific configs are only for use in checking
+ // server trust not client trust so use the default config.
+ NetworkSecurityConfig config = mConfig.getConfigForHostname("");
+ return config.getTrustManager().getAcceptedIssuers();
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 19a98f345498..329d1b06c218 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -436,7 +436,8 @@ public final class InputMethodManager {
mCurId = res.id;
mBindSequence = res.sequence;
}
- startInputInner(null, 0, 0, 0);
+ startInputInner(InputMethodClient.START_INPUT_REASON_BOUND_TO_IMMS,
+ null, 0, 0, 0);
return;
}
case MSG_UNBIND: {
@@ -461,7 +462,9 @@ public final class InputMethodManager {
startInput = mActive;
}
if (startInput) {
- startInputInner(null, 0, 0, 0);
+ startInputInner(
+ InputMethodClient.START_INPUT_REASON_UNBOUND_FROM_IMMS, null, 0, 0,
+ 0);
}
return;
}
@@ -494,7 +497,10 @@ public final class InputMethodManager {
// In that case, we really should not call
// mServedInputConnection.finishComposingText.
if (checkFocusNoStartInput(mHasBeenInactive, false)) {
- startInputInner(null, 0, 0, 0);
+ final int reason = active ?
+ InputMethodClient.START_INPUT_REASON_ACTIVATED_BY_IMMS :
+ InputMethodClient.START_INPUT_REASON_DEACTIVATED_BY_IMMS;
+ startInputInner(reason, null, 0, 0, 0);
}
}
}
@@ -1118,18 +1124,23 @@ public final class InputMethodManager {
mServedConnecting = true;
}
-
- startInputInner(null, 0, 0, 0);
+
+ startInputInner(InputMethodClient.START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API, null, 0,
+ 0, 0);
}
-
- boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
+
+ boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
+ IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {
final View view;
synchronized (mH) {
view = mServedView;
// Make sure we have a window token for the served view.
- if (DEBUG) Log.v(TAG, "Starting input: view=" + view);
+ if (DEBUG) {
+ Log.v(TAG, "Starting input: view=" + view +
+ " reason=" + InputMethodClient.getStartInputReason(startInputReason));
+ }
if (view == null) {
if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
return false;
@@ -1157,7 +1168,7 @@ public final class InputMethodManager {
vh.post(new Runnable() {
@Override
public void run() {
- startInputInner(null, 0, 0, 0);
+ startInputInner(startInputReason, null, 0, 0, 0);
}
});
return false;
@@ -1221,11 +1232,11 @@ public final class InputMethodManager {
+ Integer.toHexString(controlFlags));
InputBindResult res;
if (windowGainingFocus != null) {
- res = mService.windowGainedFocus(mClient, windowGainingFocus,
+ res = mService.windowGainedFocus(startInputReason, mClient, windowGainingFocus,
controlFlags, softInputMode, windowFlags,
tba, servedContext);
} else {
- res = mService.startInput(mClient,
+ res = mService.startInput(startInputReason, mClient,
servedContext, tba, controlFlags);
}
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
@@ -1352,7 +1363,7 @@ public final class InputMethodManager {
*/
public void checkFocus() {
if (checkFocusNoStartInput(false, true)) {
- startInputInner(null, 0, 0, 0);
+ startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
}
}
@@ -1440,8 +1451,8 @@ public final class InputMethodManager {
// should be done in conjunction with telling the system service
// about the window gaining focus, to help make the transition
// smooth.
- if (startInputInner(rootView.getWindowToken(),
- controlFlags, softInputMode, windowFlags)) {
+ if (startInputInner(InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN,
+ rootView.getWindowToken(), controlFlags, softInputMode, windowFlags)) {
return;
}
}
@@ -1451,8 +1462,10 @@ public final class InputMethodManager {
synchronized (mH) {
try {
if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
- mService.windowGainedFocus(mClient, rootView.getWindowToken(),
- controlFlags, softInputMode, windowFlags, null, null);
+ mService.windowGainedFocus(
+ InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
+ rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null,
+ null);
} catch (RemoteException e) {
}
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 60c5e420d24f..db3ecc6f2dc8 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -45,9 +45,10 @@ interface IInputMethodManager {
void addClient(in IInputMethodClient client,
in IInputContext inputContext, int uid, int pid);
void removeClient(in IInputMethodClient client);
-
- InputBindResult startInput(in IInputMethodClient client,
- IInputContext inputContext, in EditorInfo attribute, int controlFlags);
+
+ InputBindResult startInput(/* @InputMethodClient.StartInputReason */ int startInputReason,
+ in IInputMethodClient client, IInputContext inputContext, in EditorInfo attribute,
+ int controlFlags);
void finishInput(in IInputMethodClient client);
boolean showSoftInput(in IInputMethodClient client, int flags,
in ResultReceiver resultReceiver);
@@ -55,9 +56,11 @@ interface IInputMethodManager {
in ResultReceiver resultReceiver);
// Report that a window has gained focus. If 'attribute' is non-null,
// this will also do a startInput.
- InputBindResult windowGainedFocus(in IInputMethodClient client, in IBinder windowToken,
- int controlFlags, int softInputMode, int windowFlags,
- in EditorInfo attribute, IInputContext inputContext);
+ InputBindResult windowGainedFocus(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ in IInputMethodClient client, in IBinder windowToken, int controlFlags,
+ int softInputMode, int windowFlags, in EditorInfo attribute,
+ IInputContext inputContext);
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
diff --git a/core/java/com/android/internal/view/InputMethodClient.java b/core/java/com/android/internal/view/InputMethodClient.java
index a0353434adf9..c27e9aa3f2bb 100644
--- a/core/java/com/android/internal/view/InputMethodClient.java
+++ b/core/java/com/android/internal/view/InputMethodClient.java
@@ -23,6 +23,49 @@ import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.SOURCE;
public final class InputMethodClient {
+ public static final int START_INPUT_REASON_UNSPECIFIED = 0;
+ public static final int START_INPUT_REASON_WINDOW_FOCUS_GAIN = 1;
+ public static final int START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY = 2;
+ public static final int START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API = 3;
+ public static final int START_INPUT_REASON_CHECK_FOCUS = 4;
+ public static final int START_INPUT_REASON_BOUND_TO_IMMS = 5;
+ public static final int START_INPUT_REASON_UNBOUND_FROM_IMMS = 6;
+ public static final int START_INPUT_REASON_ACTIVATED_BY_IMMS = 7;
+ public static final int START_INPUT_REASON_DEACTIVATED_BY_IMMS = 8;
+
+ @Retention(SOURCE)
+ @IntDef({START_INPUT_REASON_UNSPECIFIED, START_INPUT_REASON_WINDOW_FOCUS_GAIN,
+ START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY,
+ START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API, START_INPUT_REASON_CHECK_FOCUS,
+ START_INPUT_REASON_BOUND_TO_IMMS, START_INPUT_REASON_ACTIVATED_BY_IMMS,
+ START_INPUT_REASON_DEACTIVATED_BY_IMMS})
+ public @interface StartInputReason {}
+
+ public static String getStartInputReason(@StartInputReason final int reason) {
+ switch (reason) {
+ case START_INPUT_REASON_UNSPECIFIED:
+ return "UNSPECIFIED";
+ case START_INPUT_REASON_WINDOW_FOCUS_GAIN:
+ return "WINDOW_FOCUS_GAIN";
+ case START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY:
+ return "WINDOW_FOCUS_GAIN_REPORT_ONLY";
+ case START_INPUT_REASON_APP_CALLED_RESTART_INPUT_API:
+ return "APP_CALLED_RESTART_INPUT_API";
+ case START_INPUT_REASON_CHECK_FOCUS:
+ return "CHECK_FOCUS";
+ case START_INPUT_REASON_BOUND_TO_IMMS:
+ return "BOUND_TO_IMMS";
+ case START_INPUT_REASON_UNBOUND_FROM_IMMS:
+ return "UNBOUND_FROM_IMMS";
+ case START_INPUT_REASON_ACTIVATED_BY_IMMS:
+ return "ACTIVATED_BY_IMMS";
+ case START_INPUT_REASON_DEACTIVATED_BY_IMMS:
+ return "DEACTIVATED_BY_IMMS";
+ default:
+ return "Unknown=" + reason;
+ }
+ }
+
public static final int UNBIND_REASON_UNSPECIFIED = 0;
public static final int UNBIND_REASON_SWITCH_CLIENT = 1;
public static final int UNBIND_REASON_SWITCH_IME = 2;
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index cc68fb292a8f..fc4916c79d09 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -101,6 +101,7 @@ hwui_cflags += -Wno-free-nonheap-object
ifeq (true, $(HWUI_NEW_OPS))
hwui_src_files += \
+ BakedOpDispatcher.cpp \
BakedOpRenderer.cpp \
OpReorderer.cpp \
RecordingCanvas.cpp
@@ -275,7 +276,9 @@ LOCAL_MULTILIB := both
LOCAL_MODULE_STEM_32 := hwuimicro
LOCAL_MODULE_STEM_64 := hwuimicro64
LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
-LOCAL_CFLAGS := $(hwui_cflags)
+LOCAL_CFLAGS := \
+ $(hwui_cflags) \
+ -DHWUI_NULL_GPU
LOCAL_C_INCLUDES += bionic/benchmarks/
LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
new file mode 100644
index 000000000000..b56b1e4c5f93
--- /dev/null
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -0,0 +1,264 @@
+/*
+ * 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.
+ */
+
+#include "BakedOpDispatcher.h"
+
+#include "BakedOpRenderer.h"
+#include "Caches.h"
+#include "Glop.h"
+#include "GlopBuilder.h"
+#include "renderstate/OffscreenBufferPool.h"
+#include "renderstate/RenderState.h"
+#include "utils/GLUtils.h"
+#include "VertexBuffer.h"
+
+#include <algorithm>
+#include <math.h>
+
+namespace android {
+namespace uirenderer {
+
+void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
+ renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
+ Texture* texture = renderer.getTexture(op.bitmap);
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
+ ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUnitQuad(texture->uvMapper)
+ .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("todo");
+}
+
+void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshUnitQuad()
+ .setFillPaint(*op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(op.unmappedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+namespace VertexBufferRenderFlags {
+ enum {
+ Offset = 0x1,
+ ShadowInterp = 0x2,
+ };
+}
+
+static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state,
+ const VertexBuffer& vertexBuffer, float translateX, float translateY,
+ SkPaint& paint, int vertexBufferRenderFlags) {
+ if (CC_LIKELY(vertexBuffer.getVertexCount())) {
+ bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp;
+ const int transformFlags = TransformFlags::OffsetByFudgeFactor;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshVertexBuffer(vertexBuffer, shadowInterp)
+ .setFillPaint(paint, state.alpha)
+ .setTransform(state.computedState.transform, transformFlags)
+ .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
+ .build();
+ renderer.renderGlop(state, glop);
+ }
+}
+
+static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha,
+ const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
+ SkPaint paint;
+ paint.setAntiAlias(true); // want to use AlphaVertex
+
+ // The caller has made sure casterAlpha > 0.
+ uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
+ if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
+ ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
+ }
+ if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
+ paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha));
+ renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0,
+ paint, VertexBufferRenderFlags::ShadowInterp);
+ }
+
+ uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
+ if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
+ spotShadowAlpha = Properties::overrideSpotShadowStrength;
+ }
+ if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
+ paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha));
+ renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0,
+ paint, VertexBufferRenderFlags::ShadowInterp);
+ }
+}
+
+void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
+ TessellationCache::vertexBuffer_pair_t buffers;
+ renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
+ op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
+ &op.shadowMatrixXY, &op.shadowMatrixZ,
+ op.lightCenter, renderer.getLightInfo().lightRadius,
+ buffers);
+
+ renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
+}
+
+void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
+ .setFillPaint(*op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewOffsetRect(0, 0, op.unmappedBounds)
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
+ const TextOp& op, const BakedOpState& state) {
+ renderer.caches().textureState().activateTexture(0);
+
+ PaintUtils::TextShadow textShadow;
+ if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
+ LOG_ALWAYS_FATAL("failed to query shadow attributes");
+ }
+
+ renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
+ ShadowTexture* texture = renderer.caches().dropShadowCache.get(
+ op.paint, (const char*) op.glyphs,
+ op.glyphCount, textShadow.radius, op.positions);
+ // If the drop shadow exceeds the max texture size or couldn't be
+ // allocated, skip drawing
+ if (!texture) return;
+ const AutoTexture autoCleanup(texture);
+
+ const float sx = op.x - texture->left + textShadow.dx;
+ const float sy = op.y - texture->top + textShadow.dy;
+
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedUnitQuad(nullptr)
+ .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+ .build();
+ renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
+ FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+
+ if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ renderTextShadow(renderer, fontRenderer, op, state);
+ }
+
+ float x = op.x;
+ float y = op.y;
+ const Matrix4& transform = state.computedState.transform;
+ const bool pureTranslate = transform.isPureTranslate();
+ if (CC_LIKELY(pureTranslate)) {
+ x = floorf(x + transform.getTranslateX() + 0.5f);
+ y = floorf(y + transform.getTranslateY() + 0.5f);
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(false);
+ } else if (CC_UNLIKELY(transform.isPerspective())) {
+ fontRenderer.setFont(op.paint, SkMatrix::I());
+ fontRenderer.setTextureFiltering(true);
+ } else {
+ // We only pass a partial transform to the font renderer. That partial
+ // matrix defines how glyphs are rasterized. Typically we want glyphs
+ // to be rasterized at their final size on screen, which means the partial
+ // matrix needs to take the scale factor into account.
+ // When a partial matrix is used to transform glyphs during rasterization,
+ // the mesh is generated with the inverse transform (in the case of scale,
+ // the mesh is generated at 1.0 / scale for instance.) This allows us to
+ // apply the full transform matrix at draw time in the vertex shader.
+ // Applying the full matrix in the shader is the easiest way to handle
+ // rotation and perspective and allows us to always generated quads in the
+ // font renderer which greatly simplifies the code, clipping in particular.
+ float sx, sy;
+ transform.decomposeScale(sx, sy);
+ fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
+ roundf(std::max(1.0f, sx)),
+ roundf(std::max(1.0f, sy))));
+ fontRenderer.setTextureFiltering(true);
+ }
+
+ // TODO: Implement better clipping for scaled/rotated text
+ const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect;
+ Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+ int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+ SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+ TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint);
+
+ bool hasActiveLayer = false; // TODO
+ fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y,
+ op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging
+}
+
+void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
+ OffscreenBuffer* buffer = *op.layerHandle;
+
+ // TODO: extend this to handle HW layers & paint properties which
+ // reside in node.properties().layerProperties()
+ float layerAlpha = op.alpha * state.alpha;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
+ .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+ Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
+ .build();
+ renderer.renderGlop(state, glop);
+
+ if (op.destroy) {
+ renderer.renderState().layerPool().putOrDelete(buffer);
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/BakedOpDispatcher.h b/libs/hwui/BakedOpDispatcher.h
new file mode 100644
index 000000000000..caf14bfeef6d
--- /dev/null
+++ b/libs/hwui/BakedOpDispatcher.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HWUI_BAKED_OP_DISPATCHER_H
+#define ANDROID_HWUI_BAKED_OP_DISPATCHER_H
+
+#include "BakedOpState.h"
+#include "RecordedOp.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
+ * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.
+ *
+ * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is
+ * minimal through public BakedOpRenderer APIs.
+ */
+class BakedOpDispatcher {
+public:
+ // Declares all "onBitmapOp(...)" style methods for every op type
+#define DISPATCH_METHOD(Type) \
+ static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state);
+ MAP_OPS(DISPATCH_METHOD);
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_BAKED_OP_DISPATCHER_H
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index d13d7ef81e5b..6cdc320a0bad 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -25,15 +25,10 @@
#include "VertexBuffer.h"
#include <algorithm>
-#include <math.h>
namespace android {
namespace uirenderer {
-////////////////////////////////////////////////////////////////////////////////
-// BakedOpRenderer
-////////////////////////////////////////////////////////////////////////////////
-
OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) {
LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
@@ -151,238 +146,5 @@ void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) {
if (!mRenderTarget.frameBufferId) mHasDrawn = true;
}
-////////////////////////////////////////////////////////////////////////////////
-// static BakedOpDispatcher methods
-////////////////////////////////////////////////////////////////////////////////
-
-void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
-void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
-void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("unsupported operation");
-}
-
-void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
- renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
- Texture* texture = renderer.getTexture(op.bitmap);
- if (!texture) return;
- const AutoTexture autoCleanup(texture);
-
- const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
- ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedUnitQuad(texture->uvMapper)
- .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
- .build();
- renderer.renderGlop(state, glop);
-}
-
-void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
- LOG_ALWAYS_FATAL("todo");
-}
-
-void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshUnitQuad()
- .setFillPaint(*op.paint, state.alpha)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRect(op.unmappedBounds)
- .build();
- renderer.renderGlop(state, glop);
-}
-
-namespace VertexBufferRenderFlags {
- enum {
- Offset = 0x1,
- ShadowInterp = 0x2,
- };
-}
-
-static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state,
- const VertexBuffer& vertexBuffer, float translateX, float translateY,
- SkPaint& paint, int vertexBufferRenderFlags) {
- if (CC_LIKELY(vertexBuffer.getVertexCount())) {
- bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp;
- const int transformFlags = TransformFlags::OffsetByFudgeFactor;
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshVertexBuffer(vertexBuffer, shadowInterp)
- .setFillPaint(paint, state.alpha)
- .setTransform(state.computedState.transform, transformFlags)
- .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
- .build();
- renderer.renderGlop(state, glop);
- }
-}
-
-static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha,
- const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
- SkPaint paint;
- paint.setAntiAlias(true); // want to use AlphaVertex
-
- // The caller has made sure casterAlpha > 0.
- uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
- if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
- ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
- }
- if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
- paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha));
- renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0,
- paint, VertexBufferRenderFlags::ShadowInterp);
- }
-
- uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
- if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
- spotShadowAlpha = Properties::overrideSpotShadowStrength;
- }
- if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
- paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha));
- renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0,
- paint, VertexBufferRenderFlags::ShadowInterp);
- }
-}
-
-void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
- TessellationCache::vertexBuffer_pair_t buffers;
- renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
- op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
- &op.shadowMatrixXY, &op.shadowMatrixZ,
- op.lightCenter, renderer.getLightInfo().lightRadius,
- buffers);
-
- renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
-}
-
-void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
- .setFillPaint(*op.paint, state.alpha)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewOffsetRect(0, 0, op.unmappedBounds)
- .build();
- renderer.renderGlop(state, glop);
-}
-
-static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
- const TextOp& op, const BakedOpState& state) {
- renderer.caches().textureState().activateTexture(0);
-
- PaintUtils::TextShadow textShadow;
- if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
- LOG_ALWAYS_FATAL("failed to query shadow attributes");
- }
-
- renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
- ShadowTexture* texture = renderer.caches().dropShadowCache.get(
- op.paint, (const char*) op.glyphs,
- op.glyphCount, textShadow.radius, op.positions);
- // If the drop shadow exceeds the max texture size or couldn't be
- // allocated, skip drawing
- if (!texture) return;
- const AutoTexture autoCleanup(texture);
-
- const float sx = op.x - texture->left + textShadow.dx;
- const float sy = op.y - texture->top + textShadow.dy;
-
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedUnitQuad(nullptr)
- .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
- .build();
- renderer.renderGlop(state, glop);
-}
-
-void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
- FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
-
- if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
- fontRenderer.setFont(op.paint, SkMatrix::I());
- renderTextShadow(renderer, fontRenderer, op, state);
- }
-
- float x = op.x;
- float y = op.y;
- const Matrix4& transform = state.computedState.transform;
- const bool pureTranslate = transform.isPureTranslate();
- if (CC_LIKELY(pureTranslate)) {
- x = floorf(x + transform.getTranslateX() + 0.5f);
- y = floorf(y + transform.getTranslateY() + 0.5f);
- fontRenderer.setFont(op.paint, SkMatrix::I());
- fontRenderer.setTextureFiltering(false);
- } else if (CC_UNLIKELY(transform.isPerspective())) {
- fontRenderer.setFont(op.paint, SkMatrix::I());
- fontRenderer.setTextureFiltering(true);
- } else {
- // We only pass a partial transform to the font renderer. That partial
- // matrix defines how glyphs are rasterized. Typically we want glyphs
- // to be rasterized at their final size on screen, which means the partial
- // matrix needs to take the scale factor into account.
- // When a partial matrix is used to transform glyphs during rasterization,
- // the mesh is generated with the inverse transform (in the case of scale,
- // the mesh is generated at 1.0 / scale for instance.) This allows us to
- // apply the full transform matrix at draw time in the vertex shader.
- // Applying the full matrix in the shader is the easiest way to handle
- // rotation and perspective and allows us to always generated quads in the
- // font renderer which greatly simplifies the code, clipping in particular.
- float sx, sy;
- transform.decomposeScale(sx, sy);
- fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
- roundf(std::max(1.0f, sx)),
- roundf(std::max(1.0f, sy))));
- fontRenderer.setTextureFiltering(true);
- }
-
- // TODO: Implement better clipping for scaled/rotated text
- const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect;
- Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
-
- int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
- SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
- TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint);
-
- bool hasActiveLayer = false; // TODO
- fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y,
- op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging
-}
-
-void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
- OffscreenBuffer* buffer = *op.layerHandle;
-
- // TODO: extend this to handle HW layers & paint properties which
- // reside in node.properties().layerProperties()
- float layerAlpha = op.alpha * state.alpha;
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
- .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
- Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
- .build();
- renderer.renderGlop(state, glop);
-
- if (op.destroy) {
- renderer.renderState().layerPool().putOrDelete(buffer);
- }
-}
-
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index 29f9a6f81242..62d183846e27 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -91,21 +91,6 @@ private:
const LightInfo mLightInfo;
};
-/**
- * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
- * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.
- *
- * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is
- * minimal through public BakedOpRenderer APIs.
- */
-class BakedOpDispatcher {
-public:
- // Declares all "onBitmapOp(...)" style methods for every op type
-#define DISPATCH_METHOD(Type) \
- static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state);
- MAP_OPS(DISPATCH_METHOD);
-};
-
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 00c4e2d47e4c..60cc7bab64dd 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -132,7 +132,7 @@ public:
DisplayList();
~DisplayList();
- // index of DisplayListOp restore, after which projected descendents should be drawn
+ // index of DisplayListOp restore, after which projected descendants should be drawn
int projectionReceiveIndex;
const LsaVector<Chunk>& getChunks() const { return chunks; }
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index bf98f79d4d5a..72fc100ebd1d 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -55,6 +55,7 @@ class DeferredDisplayList;
class DeferredLayerUpdater;
class DisplayListOp;
class DrawOp;
+class DrawRenderNodeOp;
class RenderNode;
class StateOp;
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 977b53c31f46..bd11d0acf176 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1386,19 +1386,19 @@ public:
: DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
, renderNode(renderNode)
, mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple())
- , mTransformFromParent(transformFromParent)
- , mSkipInOrderDraw(false) {}
+ , localMatrix(transformFromParent)
+ , skipInOrderDraw(false) {}
virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
bool useQuickReject) override {
- if (renderNode->isRenderable() && !mSkipInOrderDraw) {
+ if (renderNode->isRenderable() && !skipInOrderDraw) {
renderNode->defer(deferStruct, level + 1);
}
}
virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
bool useQuickReject) override {
- if (renderNode->isRenderable() && !mSkipInOrderDraw) {
+ if (renderNode->isRenderable() && !skipInOrderDraw) {
renderNode->replay(replayStruct, level + 1);
}
}
@@ -1439,7 +1439,7 @@ private:
/**
* Records transform vs parent, used for computing total transform without rerunning DL contents
*/
- const mat4 mTransformFromParent;
+ const mat4 localMatrix;
/**
* Holds the transformation between the projection surface ViewGroup and this RenderNode
@@ -1449,8 +1449,8 @@ private:
*
* Note: doesn't include transformation within the RenderNode, or its properties.
*/
- mat4 mTransformFromCompositingAncestor;
- bool mSkipInOrderDraw;
+ mat4 transformFromCompositingAncestor;
+ bool skipInOrderDraw;
};
/**
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 5f33cae2f91a..47654f400ec2 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -29,8 +29,9 @@
#if HWUI_NEW_OPS
-#include "BakedOpState.h"
+#include "BakedOpDispatcher.h"
#include "BakedOpRenderer.h"
+#include "BakedOpState.h"
#else
#include "OpenGLRenderer.h"
#endif
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 5e954ae6e971..9cbd9c2d9ffc 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -330,6 +330,7 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
for (int i = layers.entries().size() - 1; i >= 0; i--) {
RenderNode* layerNode = layers.entries()[i].renderNode;
const Rect& layerDamage = layers.entries()[i].damage;
+ layerNode->computeOrdering();
// map current light center into RenderNode's coordinate space
Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter();
@@ -339,7 +340,7 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
layerDamage, lightCenter, nullptr, layerNode);
if (layerNode->getDisplayList()) {
- deferDisplayList(*(layerNode->getDisplayList()));
+ deferNodeOps(*layerNode);
}
restoreForLayer();
}
@@ -347,6 +348,7 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
// Defer Fbo0
for (const sp<RenderNode>& node : nodes) {
if (node->nothingToDraw()) continue;
+ node->computeOrdering();
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
deferNodePropsAndOps(*node);
@@ -354,20 +356,6 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
}
}
-OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
- const Vector3& lightCenter)
- : mCanvasState(*this) {
- ATRACE_NAME("prepare drawing commands");
- // Prepare to defer Fbo0
- mLayerReorderers.emplace_back(viewportWidth, viewportHeight,
- Rect(viewportWidth, viewportHeight));
- mLayerStack.push_back(0);
- mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
- 0, 0, viewportWidth, viewportHeight, lightCenter);
-
- deferDisplayList(displayList);
-}
-
void OpReorderer::onViewportInitialized() {}
void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
@@ -462,10 +450,10 @@ void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
Matrix4::identity(),
saveLayerBounds,
&saveLayerPaint));
- deferDisplayList(*(node.getDisplayList()));
+ deferNodeOps(node);
onEndLayerOp(*new (mAllocator) EndLayerOp());
} else {
- deferDisplayList(*(node.getDisplayList()));
+ deferNodeOps(node);
}
}
}
@@ -610,18 +598,53 @@ void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
}
}
+void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) {
+ const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
+ int count = mCanvasState.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+
+ // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
+ const DisplayList& displayList = *(renderNode.getDisplayList());
+
+ const RecordedOp* op = (displayList.getOps()[displayList.projectionReceiveIndex]);
+ const RenderNodeOp* backgroundOp = static_cast<const RenderNodeOp*>(op);
+ const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
+
+ // Transform renderer to match background we're projecting onto
+ // (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
+ mCanvasState.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
+
+ // If the projection receiver has an outline, we mask projected content to it
+ // (which we know, apriori, are all tessellated paths)
+ mCanvasState.setProjectionPathMask(mAllocator, projectionReceiverOutline);
+
+ // draw projected nodes
+ for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
+ RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
+
+ int restoreTo = mCanvasState.save(SkCanvas::kMatrix_SaveFlag);
+ mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
+ deferRenderNodeOp(*childOp);
+ mCanvasState.restoreToCount(restoreTo);
+ }
+
+ mCanvasState.restoreToCount(count);
+}
+
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
- * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
- * BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
+ * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas.
+ * E.g. a BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
*/
#define OP_RECEIVER(Type) \
[](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
-void OpReorderer::deferDisplayList(const DisplayList& displayList) {
+void OpReorderer::deferNodeOps(const RenderNode& renderNode) {
static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
MAP_OPS(OP_RECEIVER)
};
+
+ // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
+ const DisplayList& displayList = *(renderNode.getDisplayList());
for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
@@ -630,6 +653,12 @@ void OpReorderer::deferDisplayList(const DisplayList& displayList) {
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
const RecordedOp* op = displayList.getOps()[opIndex];
receivers[op->opId](*this, *op);
+
+ if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty()
+ && displayList.projectionReceiveIndex >= 0
+ && static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) {
+ deferProjectedChildren(renderNode);
+ }
}
defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
}
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 976f41323efa..00df8b0951e6 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -124,9 +124,6 @@ public:
uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter);
- OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
- const Vector3& lightCenter);
-
virtual ~OpReorderer() {}
/**
@@ -202,15 +199,17 @@ private:
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
}
- // should always be surrounded by a save/restore pair
+ // should always be surrounded by a save/restore pair, and not called if DisplayList is null
void deferNodePropsAndOps(RenderNode& node);
+ template <typename V>
+ void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
+
void deferShadow(const RenderNodeOp& casterOp);
- void deferDisplayList(const DisplayList& displayList);
+ void deferProjectedChildren(const RenderNode& renderNode);
- template <typename V>
- void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
+ void deferNodeOps(const RenderNode& renderNode);
void deferRenderNodeOp(const RenderNodeOp& op);
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 127dca5be440..b4a201ed2374 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -97,7 +97,17 @@ struct RenderNodeOp : RecordedOp {
RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode)
: SUPER_PAINTLESS(RenderNodeOp)
, renderNode(renderNode) {}
- RenderNode * renderNode; // not const, since drawing modifies it (somehow...)
+ RenderNode * renderNode; // not const, since drawing modifies it
+
+ /**
+ * Holds the transformation between the projection surface ViewGroup and this RenderNode
+ * drawing instance. Represents any translations / transformations done within the drawing of
+ * the compositing ancestor ViewGroup's draw, before the draw of the View represented by this
+ * DisplayList draw instance.
+ *
+ * Note: doesn't include transformation within the RenderNode, or its properties.
+ */
+ Matrix4 transformFromCompositingAncestor;
bool skipInOrderDraw = false;
};
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 736cc9ecaf2a..6d0e9e0e0f7a 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -205,10 +205,6 @@ private:
return dstBuffer;
}
- inline char* refText(const char* text, size_t byteLength) {
- return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength);
- }
-
inline const SkPath* refPath(const SkPath* path) {
if (!path) return nullptr;
@@ -220,13 +216,8 @@ private:
}
/**
- * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in (with deduping
- * based on paint generation ID)
- *
- * Note that this forces Left_Align, since drawText glyph rendering expects left alignment,
- * since alignment offsetting has been done at a higher level. This is done to essentially all
- * copied paints, since the deduping can mean a paint is shared by drawText commands and other
- * types (which wouldn't care about alignment).
+ * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in
+ * (with deduping based on paint hash / equality check)
*/
inline const SkPaint* refPaint(const SkPaint* paint) {
if (!paint) return nullptr;
@@ -246,11 +237,8 @@ private:
// In the unlikely event that 2 unique paints have the same hash we do a
// object equality check to ensure we don't erroneously dedup them.
if (cachedPaint == nullptr || *cachedPaint != *paint) {
- SkPaint* copy = new SkPaint(*paint);
- copy->setTextAlign(SkPaint::kLeft_Align);
-
- cachedPaint = copy;
- mDisplayList->paints.emplace_back(copy);
+ cachedPaint = new SkPaint(*paint);
+ mDisplayList->paints.emplace_back(cachedPaint);
// replaceValueFor() performs an add if the entry doesn't exist
mPaintMap.replaceValueFor(key, cachedPaint);
refBitmapsInShader(cachedPaint->getShader());
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 3f24f44e52da..ae690fdef4c7 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -487,7 +487,7 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL
info.damageAccumulator->pushTransform(&op->localMatrix);
bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
#else
- info.damageAccumulator->pushTransform(&op->mTransformFromParent);
+ info.damageAccumulator->pushTransform(&op->localMatrix);
bool childFunctorsNeedLayer = functorsNeedLayer
// Recorded with non-rect clip, or canvas-rotated by parent
|| op->mRecordedWithPotentialStencilClip;
@@ -658,7 +658,6 @@ void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform)
* which are flagged to not draw in the standard draw loop.
*/
void RenderNode::computeOrdering() {
-#if !HWUI_NEW_OPS
ATRACE_CALL();
mProjectedNodes.clear();
@@ -666,43 +665,41 @@ void RenderNode::computeOrdering() {
// transform properties are applied correctly to top level children
if (mDisplayList == nullptr) return;
for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
- DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
+ renderNodeOp_t* childOp = mDisplayList->getChildren()[i];
childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
}
-#endif
}
void RenderNode::computeOrderingImpl(
- DrawRenderNodeOp* opState,
- std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
+ renderNodeOp_t* opState,
+ std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface) {
-#if !HWUI_NEW_OPS
mProjectedNodes.clear();
if (mDisplayList == nullptr || mDisplayList->isEmpty()) return;
// TODO: should avoid this calculation in most cases
// TODO: just calculate single matrix, down to all leaf composited elements
Matrix4 localTransformFromProjectionSurface(*transformFromProjectionSurface);
- localTransformFromProjectionSurface.multiply(opState->mTransformFromParent);
+ localTransformFromProjectionSurface.multiply(opState->localMatrix);
if (properties().getProjectBackwards()) {
// composited projectee, flag for out of order draw, save matrix, and store in proj surface
- opState->mSkipInOrderDraw = true;
- opState->mTransformFromCompositingAncestor = localTransformFromProjectionSurface;
+ opState->skipInOrderDraw = true;
+ opState->transformFromCompositingAncestor = localTransformFromProjectionSurface;
compositedChildrenOfProjectionSurface->push_back(opState);
} else {
// standard in order draw
- opState->mSkipInOrderDraw = false;
+ opState->skipInOrderDraw = false;
}
if (mDisplayList->getChildren().size() > 0) {
const bool isProjectionReceiver = mDisplayList->projectionReceiveIndex >= 0;
bool haveAppliedPropertiesToProjection = false;
for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
- DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
+ renderNodeOp_t* childOp = mDisplayList->getChildren()[i];
RenderNode* child = childOp->renderNode;
- std::vector<DrawRenderNodeOp*>* projectionChildren = nullptr;
+ std::vector<renderNodeOp_t*>* projectionChildren = nullptr;
const mat4* projectionTransform = nullptr;
if (isProjectionReceiver && !child->properties().getProjectBackwards()) {
// if receiving projections, collect projecting descendant
@@ -723,7 +720,6 @@ void RenderNode::computeOrderingImpl(
child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
}
}
-#endif
}
class DeferOperationHandler {
@@ -793,10 +789,10 @@ void RenderNode::buildZSortedChildList(const DisplayList::Chunk& chunk,
if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
zTranslatedNodes.push_back(ZDrawRenderNodeOpPair(childZ, childOp));
- childOp->mSkipInOrderDraw = true;
+ childOp->skipInOrderDraw = true;
} else if (!child->properties().getProjectBackwards()) {
// regular, in order drawing DisplayList
- childOp->mSkipInOrderDraw = false;
+ childOp->skipInOrderDraw = false;
}
}
@@ -913,7 +909,7 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
// attempt to render the shadow if the caster about to be drawn is its caster,
// OR if its caster's Z value is similar to the previous potential caster
if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) {
- caster->issueDrawShadowOperation(casterOp->mTransformFromParent, handler);
+ caster->issueDrawShadowOperation(casterOp->localMatrix, handler);
lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
shadowIndex++;
@@ -927,10 +923,10 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
DrawRenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
- renderer.concatMatrix(childOp->mTransformFromParent);
- childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+ renderer.concatMatrix(childOp->localMatrix);
+ childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone
handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
- childOp->mSkipInOrderDraw = true;
+ childOp->skipInOrderDraw = true;
renderer.restoreToCount(restoreTo);
drawIndex++;
@@ -967,14 +963,14 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T&
// draw projected nodes
for (size_t i = 0; i < mProjectedNodes.size(); i++) {
- DrawRenderNodeOp* childOp = mProjectedNodes[i];
+ renderNodeOp_t* childOp = mProjectedNodes[i];
// matrix save, concat, and restore can be done safely without allocating operations
int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
- renderer.concatMatrix(childOp->mTransformFromCompositingAncestor);
- childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+ renderer.concatMatrix(childOp->transformFromCompositingAncestor);
+ childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone
handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
- childOp->mSkipInOrderDraw = true;
+ childOp->skipInOrderDraw = true;
renderer.restoreToCount(restoreTo);
}
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 83d1b5888b64..b6f50b111ab5 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -51,20 +51,22 @@ class OpReorderer;
class Rect;
class SkiaShader;
-
#if HWUI_NEW_OPS
class OffscreenBuffer;
+struct RenderNodeOp;
typedef OffscreenBuffer layer_t;
+typedef RenderNodeOp renderNodeOp_t;
#else
class Layer;
typedef Layer layer_t;
+typedef DrawRenderNodeOp renderNodeOp_t;
#endif
class ClipRectOp;
+class DrawRenderNodeOp;
class SaveLayerOp;
class SaveOp;
class RestoreToCountOp;
-class DrawRenderNodeOp;
class TreeInfo;
namespace proto {
@@ -85,6 +87,7 @@ class RenderNode;
*/
class RenderNode : public VirtualLightRefBase {
friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
+friend class OpReorderer;
public:
enum DirtyPropertyMask {
GENERIC = 1 << 1,
@@ -221,8 +224,8 @@ private:
PositiveZChildren
};
- void computeOrderingImpl(DrawRenderNodeOp* opState,
- std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
+ void computeOrderingImpl(renderNodeOp_t* opState,
+ std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface);
template <class T>
@@ -305,7 +308,7 @@ private:
*/
// for projection surfaces, contains a list of all children items
- std::vector<DrawRenderNodeOp*> mProjectedNodes;
+ std::vector<renderNodeOp_t*> mProjectedNodes;
// How many references our parent(s) have to us. Typically this should alternate
// between 2 and 1 (when a staging push happens we inc first then dec)
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index eea0c7f9ccbe..53b64c32ce52 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -17,11 +17,14 @@
#include <benchmark/Benchmark.h>
#include "BakedOpState.h"
+#include "BakedOpDispatcher.h"
#include "BakedOpRenderer.h"
+#include "LayerUpdateQueue.h"
#include "OpReorderer.h"
#include "RecordedOp.h"
#include "RecordingCanvas.h"
#include "utils/TestUtils.h"
+#include "Vector.h"
#include "microbench/MicroBench.h"
#include <vector>
@@ -29,26 +32,38 @@
using namespace android;
using namespace android::uirenderer;
-auto sReorderingDisplayList = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
- SkPaint paint;
+const LayerUpdateQueue sEmptyLayerUpdateQueue;
+const Vector3 sLightCenter = {100, 100, 100};
- // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
- // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
- canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- for (int i = 0; i < 30; i++) {
- canvas.translate(0, 10);
- canvas.drawRect(0, 0, 10, 10, paint);
- canvas.drawBitmap(bitmap, 5, 0, nullptr);
- }
- canvas.restore();
-});
+static std::vector<sp<RenderNode>> createTestNodeList() {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
+ SkPaint paint;
+
+ // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
+ // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
+ canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ for (int i = 0; i < 30; i++) {
+ canvas.translate(0, 10);
+ canvas.drawRect(0, 0, 10, 10, paint);
+ canvas.drawBitmap(bitmap, 5, 0, nullptr);
+ }
+ canvas.restore();
+ });
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ std::vector<sp<RenderNode>> vec;
+ vec.emplace_back(node);
+ return vec;
+}
BENCHMARK_NO_ARG(BM_OpReorderer_defer);
void BM_OpReorderer_defer::Run(int iters) {
+ auto nodes = createTestNodeList();
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ nodes, sLightCenter);
MicroBench::DoNotOptimize(&reorderer);
}
StopBenchmarkTiming();
@@ -57,13 +72,16 @@ void BM_OpReorderer_defer::Run(int iters) {
BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender);
void BM_OpReorderer_deferAndRender::Run(int iters) {
TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
+ auto nodes = createTestNodeList();
+ BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128 };
+
RenderState& renderState = thread.renderState();
Caches& caches = Caches::getInstance();
- BakedOpRenderer::LightInfo lightInfo = { 50.0f, 128, 128 };
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ nodes, sLightCenter);
BakedOpRenderer renderer(caches, renderState, true, lightInfo);
reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index c3cfc940281e..d36ce993164e 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -28,6 +28,7 @@
#include "renderthread/RenderThread.h"
#if HWUI_NEW_OPS
+#include "BakedOpDispatcher.h"
#include "BakedOpRenderer.h"
#endif
diff --git a/libs/hwui/tests/scenes/OvalAnimation.cpp b/libs/hwui/tests/scenes/OvalAnimation.cpp
index 919a53d118b3..936aba184c88 100644
--- a/libs/hwui/tests/scenes/OvalAnimation.cpp
+++ b/libs/hwui/tests/scenes/OvalAnimation.cpp
@@ -29,17 +29,14 @@ public:
sp<RenderNode> card;
void createContent(int width, int height, TestCanvas& canvas) override {
canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- canvas.insertReorderBarrier(true);
-
- card = TestUtils::createNode(0, 0, 200, 200, [](TestCanvas& canvas) {
+ card = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, TestCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(0xFF000000);
canvas.drawOval(0, 0, 200, 200, paint);
});
-
canvas.drawRenderNode(card.get());
- canvas.insertReorderBarrier(false);
}
void doFrame(int frameNr) override {
diff --git a/libs/hwui/tests/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
index 0fba4eb5f9ff..c31ddd1d531b 100644
--- a/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
+++ b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
@@ -44,7 +44,7 @@ public:
SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4];
sp<RenderNode> card = TestUtils::createNode(x, y,
x + dp(100), y + dp(100),
- [color](TestCanvas& canvas) {
+ [color](RenderProperties& props, TestCanvas& canvas) {
canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
});
canvas.drawRenderNode(card.get());
diff --git a/libs/hwui/tests/scenes/RectGridAnimation.cpp b/libs/hwui/tests/scenes/RectGridAnimation.cpp
index 254f8280cb66..a1f04d67c785 100644
--- a/libs/hwui/tests/scenes/RectGridAnimation.cpp
+++ b/libs/hwui/tests/scenes/RectGridAnimation.cpp
@@ -34,7 +34,7 @@ public:
canvas.insertReorderBarrier(true);
card = TestUtils::createNode(50, 50, 250, 250,
- [](TestCanvas& canvas) {
+ [](RenderProperties& props, TestCanvas& canvas) {
canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
SkRegion region;
diff --git a/libs/hwui/tests/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
index c62dd19968c3..c73e97ba003c 100644
--- a/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
+++ b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
@@ -32,7 +32,7 @@ public:
canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
card = TestUtils::createNode(0, 0, 200, 200,
- [](TestCanvas& canvas) {
+ [](RenderProperties& props, TestCanvas& canvas) {
canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
index 05fd08a3340a..cc15cc6d2628 100644
--- a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
+++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
@@ -31,7 +31,7 @@ TEST(LayerUpdateQueue, construct) {
// sync node properties, so properties() reflects correct width and height
static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) {
- sp<RenderNode> node = TestUtils::createNode(0, 0, width, height);
+ sp<RenderNode> node = TestUtils::createNode(0, 0, width, height, nullptr);
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
return node;
}
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index d76086c9cfcd..2ce1d0a5db7f 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -111,25 +111,29 @@ TEST(OpReorderer, simple) {
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 100, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(25, 25);
canvas.drawRect(0, 0, 100, 200, SkPaint());
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
- OpReorderer reorderer(100, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ createSyncedNodeList(node), sLightCenter);
SimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
}
TEST(OpReorderer, simpleRejection) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -147,7 +151,8 @@ TEST(OpReorderer, simpleBatching) {
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
@@ -161,7 +166,8 @@ TEST(OpReorderer, simpleBatching) {
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
SimpleBatchingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -179,16 +185,19 @@ TEST(OpReorderer, textStrikethroughBatching) {
EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects";
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 2000, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 2000,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint textPaint;
textPaint.setAntiAlias(true);
textPaint.setTextSize(20);
textPaint.setStrikeThruText(true);
+ textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < LOOPS; i++) {
TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
}
});
- OpReorderer reorderer(200, 2000, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
+ createSyncedNodeList(node), sLightCenter);
TextStrikethroughTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -214,14 +223,16 @@ TEST(OpReorderer, renderNode) {
}
};
- sp<RenderNode> child = TestUtils::createNode(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+ auto child = TestUtils::createNode(10, 10, 110, 110,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
});
RenderNode* childPtr = child.get();
- sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [childPtr](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, 200, 200, paint);
@@ -249,7 +260,8 @@ TEST(OpReorderer, clipped) {
}
};
- sp<RenderNode> node = TestUtils::createNode(0, 0, 200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
canvas.drawBitmap(bitmap, 0, 0, nullptr);
});
@@ -291,13 +303,14 @@ TEST(OpReorderer, saveLayerSimple) {
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(10, 10, 190, 190, SkPaint());
canvas.restore();
});
-
- OpReorderer reorderer(200, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
SaveLayerSimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
@@ -354,7 +367,8 @@ TEST(OpReorderer, saveLayerNested) {
}
};
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(800, 800, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 800, 800,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
{
canvas.drawRect(0, 0, 800, 800, SkPaint());
@@ -367,14 +381,16 @@ TEST(OpReorderer, saveLayerNested) {
canvas.restore();
});
- OpReorderer reorderer(800, 800, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800,
+ createSyncedNodeList(node), sLightCenter);
SaveLayerNestedTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex());
}
TEST(OpReorderer, saveLayerContentRejection) {
- auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op);
canvas.saveLayerAlpha(200, 200, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
@@ -385,7 +401,8 @@ TEST(OpReorderer, saveLayerContentRejection) {
canvas.restore();
canvas.restore();
});
- OpReorderer reorderer(200, 200, *dl, sLightCenter);
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
// should see no ops, even within the layer, since the layer should be rejected
@@ -424,7 +441,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
}
};
- sp<RenderNode> node = TestUtils::createNode(10, 10, 110, 110,
+ auto node = TestUtils::createNode(10, 10, 110, 110,
[](RenderProperties& props, RecordingCanvas& canvas) {
props.mutateLayerProperties().setType(LayerType::RenderLayer);
SkPaint paint;
@@ -562,7 +579,7 @@ static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder)
}
static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
auto node = TestUtils::createNode(0, 0, 100, 100,
- [expectedDrawOrder](RecordingCanvas& canvas) {
+ [expectedDrawOrder](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedRect(&canvas, expectedDrawOrder);
});
node->mutateStagingProperties().setTranslationZ(z);
@@ -579,7 +596,7 @@ TEST(OpReorderer, zReorder) {
};
auto parent = TestUtils::createNode(0, 0, 100, 100,
- [](RecordingCanvas& canvas) {
+ [](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
drawOrderedRect(&canvas, 1);
canvas.insertReorderBarrier(true);
@@ -600,10 +617,93 @@ TEST(OpReorderer, zReorder) {
EXPECT_EQ(10, renderer.getIndex());
};
+TEST(OpReorderer, projectionReorder) {
+ static const int scrollX = 5;
+ static const int scrollY = 10;
+ class ProjectionReorderTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ const int index = mIndex++;
+
+ Matrix4 expectedMatrix;
+ switch (index) {
+ case 0:
+ EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
+ EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
+ expectedMatrix.loadIdentity();
+ break;
+ case 1:
+ EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds);
+ EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
+ expectedMatrix.loadTranslate(50, 50, 0); // TODO: should scroll be respected here?
+ break;
+ case 2:
+ EXPECT_EQ(Rect(100, 50), op.unmappedBounds);
+ EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
+ expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0);
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, state.computedState.transform);
+ }
+ };
+
+ /**
+ * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
+ * with a projecting child (P) of its own. P would normally draw between B and C's "background"
+ * draw, but because it is projected backwards, it's drawn in between B and C.
+ *
+ * The parent is scrolled by scrollX/scrollY, but this does not affect the background
+ * (which isn't affected by scroll).
+ */
+ auto receiverBackground = TestUtils::createNode(0, 0, 100, 100,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setProjectionReceiver(true);
+ // scroll doesn't apply to background, so undone via translationX/Y
+ // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+ properties.setTranslationX(scrollX);
+ properties.setTranslationY(scrollY);
+
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ auto projectingRipple = TestUtils::createNode(50, 0, 100, 50,
+ [](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setProjectBackwards(true);
+ properties.setClipToBounds(false);
+ SkPaint paint;
+ paint.setColor(SK_ColorDKGRAY);
+ canvas.drawRect(-10, -10, 60, 60, paint);
+ });
+ auto child = TestUtils::createNode(0, 50, 100, 100,
+ [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorBLUE);
+ canvas.drawRect(0, 0, 100, 50, paint);
+ canvas.drawRenderNode(projectingRipple.get());
+ });
+ auto parent = TestUtils::createNode(0, 0, 100, 100,
+ [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
+ canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
+ canvas.drawRenderNode(receiverBackground.get());
+ canvas.drawRenderNode(child.get());
+ canvas.restore();
+ });
+
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+ createSyncedNodeList(parent), sLightCenter);
+ ProjectionReorderTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(3, renderer.getIndex());
+}
+
// creates a 100x100 shadow casting node with provided translationZ
static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
return TestUtils::createNode(0, 0, 100, 100,
- [translationZ] (RenderProperties& properties, RecordingCanvas& canvas) {
+ [translationZ](RenderProperties& properties, RecordingCanvas& canvas) {
properties.setTranslationZ(translationZ);
properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
SkPaint paint;
@@ -630,8 +730,8 @@ TEST(OpReorderer, shadow) {
}
};
- sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
- [] (RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
});
@@ -666,8 +766,8 @@ TEST(OpReorderer, shadowSaveLayer) {
}
};
- sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
- [] (RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
// save/restore outside of reorderBarrier, so they don't get moved out of place
canvas.translate(20, 10);
int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
@@ -706,7 +806,7 @@ RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) {
}
};
- sp<RenderNode> parent = TestUtils::createNode(50, 60, 150, 160,
+ auto parent = TestUtils::createNode(50, 60, 150, 160,
[](RenderProperties& props, RecordingCanvas& canvas) {
props.mutateLayerProperties().setType(LayerType::RenderLayer);
canvas.insertReorderBarrier(true);
@@ -749,8 +849,8 @@ TEST(OpReorderer, shadowLayering) {
EXPECT_TRUE(index == 2 || index == 3);
}
};
- sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
- [] (RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
@@ -954,7 +1054,7 @@ TEST(OpReorderer, renderPropSaveLayerAlphaRotate) {
SaveLayerAlphaData observedData;
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
// Translate and rotate the view so that the only visible part is the top left corner of
- // the view. It will form an isoceles right triangle with a long side length of 200 at the
+ // the view. It will form an isosceles right triangle with a long side length of 200 at the
// bottom of the viewport.
properties.setTranslationX(100);
properties.setTranslationY(100);
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index c23d47e3fab4..81f0851a6174 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -75,6 +75,7 @@ TEST(RecordingCanvas, drawText) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
});
@@ -95,6 +96,7 @@ TEST(RecordingCanvas, drawText_strikeThruAndUnderline) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
paint.setUnderlineText(i != 0);
@@ -126,6 +128,7 @@ TEST(RecordingCanvas, drawText_forceAlignLeft) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setTextSize(20);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setTextAlign(SkPaint::kLeft_Align);
TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
paint.setTextAlign(SkPaint::kCenter_Align);
@@ -135,12 +138,17 @@ TEST(RecordingCanvas, drawText_forceAlignLeft) {
});
int count = 0;
- playbackOps(*dl, [&count](const RecordedOp& op) {
+ float lastX = FLT_MAX;
+ playbackOps(*dl, [&count, &lastX](const RecordedOp& op) {
count++;
ASSERT_EQ(RecordedOpId::TextOp, op.opId);
EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
<< "recorded drawText commands must force kLeft_Align on their paint";
- EXPECT_EQ(SkPaint::kGlyphID_TextEncoding, op.paint->getTextEncoding()); // verify TestUtils
+
+ // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class)
+ EXPECT_GT(lastX, ((const TextOp&)op).x)
+ << "x coordinate should reduce across each of the draw commands, from alignment";
+ lastX = ((const TextOp&)op).x;
});
ASSERT_EQ(3, count);
}
@@ -313,6 +321,49 @@ TEST(RecordingCanvas, saveLayer_rotateClipped) {
EXPECT_EQ(3, count);
}
+TEST(RecordingCanvas, drawRenderNode_projection) {
+ sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ SkPaint paint;
+ paint.setColor(SK_ColorWHITE);
+ canvas.drawRect(0, 0, 100, 100, paint);
+ });
+ {
+ background->mutateStagingProperties().setProjectionReceiver(false);
+
+ // NO RECEIVER PRESENT
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
+ [&background](RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.drawRenderNode(background.get());
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ });
+ EXPECT_EQ(-1, dl->projectionReceiveIndex)
+ << "no projection receiver should have been observed";
+ }
+ {
+ background->mutateStagingProperties().setProjectionReceiver(true);
+
+ // RECEIVER PRESENT
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
+ [&background](RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.drawRenderNode(background.get());
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ });
+
+ ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops";
+ auto op = dl->getOps()[1];
+ EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId);
+ EXPECT_EQ(1, dl->projectionReceiveIndex)
+ << "correct projection receiver not identified";
+
+ // verify the behavior works even though projection receiver hasn't been sync'd yet
+ EXPECT_TRUE(background->stagingProperties().isProjectionReceiver());
+ EXPECT_FALSE(background->properties().isProjectionReceiver());
+ }
+}
+
TEST(RecordingCanvas, insertReorderBarrier) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 400, 400, SkPaint());
@@ -334,5 +385,39 @@ TEST(RecordingCanvas, insertReorderBarrier) {
EXPECT_TRUE(chunks[1].reorderChildren);
}
+TEST(RecordingCanvas, refPaint) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setTextSize(20);
+ paint.setTextAlign(SkPaint::kLeft_Align);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) {
+ paint.setColor(SK_ColorBLUE);
+ // first three should use same paint
+ canvas.drawRect(0, 0, 200, 10, paint);
+ SkPaint paintCopy(paint);
+ canvas.drawRect(0, 10, 200, 20, paintCopy);
+ TestUtils::drawTextToCanvas(&canvas, "helloworld", paint, 50, 25);
+
+ // only here do we use different paint ptr
+ paint.setColor(SK_ColorRED);
+ canvas.drawRect(0, 20, 200, 30, paint);
+ });
+ auto ops = dl->getOps();
+ ASSERT_EQ(4u, ops.size());
+
+ // first three are the same
+ EXPECT_NE(nullptr, ops[0]->paint);
+ EXPECT_NE(&paint, ops[0]->paint);
+ EXPECT_EQ(ops[0]->paint, ops[1]->paint);
+ EXPECT_EQ(ops[0]->paint, ops[2]->paint);
+
+ // last is different, but still copied / non-null
+ EXPECT_NE(nullptr, ops[3]->paint);
+ EXPECT_NE(ops[0]->paint, ops[3]->paint);
+ EXPECT_NE(&paint, ops[3]->paint);
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp
index dd6fc36b8042..6cef85203352 100644
--- a/libs/hwui/utils/TestUtils.cpp
+++ b/libs/hwui/utils/TestUtils.cpp
@@ -37,44 +37,56 @@ SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end)
}
void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
- const SkPaint& inPaint, float x, float y) {
- // copy to force TextEncoding (which JNI layer would have done)
- SkPaint paint(inPaint);
- paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ const SkPaint& paint, float x, float y) {
+ // drawing text requires GlyphID TextEncoding (which JNI layer would have done)
+ LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding,
+ "must use glyph encoding");
- SkMatrix identity;
- identity.setIdentity();
- SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
- SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
+ SkMatrix identity;
+ identity.setIdentity();
+ SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+ SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
- float totalAdvance = 0;
- std::vector<glyph_t> glyphs;
- std::vector<float> positions;
- Rect bounds;
- while (*text != '\0') {
- SkUnichar unichar = SkUTF8_NextUnichar(&text);
- glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar);
- autoCache.getCache()->unicharToGlyph(unichar);
+ float totalAdvance = 0;
+ std::vector<glyph_t> glyphs;
+ std::vector<float> positions;
+ Rect bounds;
+ while (*text != '\0') {
+ SkUnichar unichar = SkUTF8_NextUnichar(&text);
+ glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar);
+ autoCache.getCache()->unicharToGlyph(unichar);
- // push glyph and its relative position
- glyphs.push_back(glyph);
- positions.push_back(totalAdvance);
- positions.push_back(0);
+ // push glyph and its relative position
+ glyphs.push_back(glyph);
+ positions.push_back(totalAdvance);
+ positions.push_back(0);
- // compute bounds
- SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar);
- Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight);
- glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop);
- bounds.unionWith(glyphBounds);
+ // compute bounds
+ SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar);
+ Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight);
+ glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop);
+ bounds.unionWith(glyphBounds);
- // advance next character
- SkScalar skWidth;
- paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL);
- totalAdvance += skWidth;
- }
- bounds.translate(x, y);
- canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), paint, x, y,
- bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
+ // advance next character
+ SkScalar skWidth;
+ paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL);
+ totalAdvance += skWidth;
+ }
+
+ // apply alignment via x parameter (which JNI layer would have done)
+ if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+ x -= totalAdvance / 2;
+ } else if (paint.getTextAlign() == SkPaint::kRight_Align) {
+ x -= totalAdvance;
+ }
+
+ bounds.translate(x, y);
+
+ // Force left alignment, since alignment offset is already baked in
+ SkPaint alignPaintCopy(paint);
+ alignPaintCopy.setTextAlign(SkPaint::kLeft_Align);
+ canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), alignPaintCopy, x, y,
+ bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
}
} /* namespace uirenderer */
diff --git a/libs/hwui/utils/TestUtils.h b/libs/hwui/utils/TestUtils.h
index f9fa242ec833..9c1c0b9d2c08 100644
--- a/libs/hwui/utils/TestUtils.h
+++ b/libs/hwui/utils/TestUtils.h
@@ -121,7 +121,7 @@ public:
}
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(RenderProperties& props, TestCanvas& canvas)> setup = nullptr) {
+ std::function<void(RenderProperties& props, TestCanvas& canvas)> setup) {
#if HWUI_NULL_GPU
// if RenderNodes are being sync'd/used, device info will be needed, since
// DeviceInfo::maxTextureSize() affects layer property
@@ -140,22 +140,6 @@ public:
return node;
}
- static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(RenderProperties& props)> setup) {
- return createNode(left, top, right, bottom,
- [&setup](RenderProperties& props, TestCanvas& canvas) {
- setup(props);
- });
- }
-
- static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(TestCanvas& canvas)> setup) {
- return createNode(left, top, right, bottom,
- [&setup](RenderProperties& props, TestCanvas& canvas) {
- setup(canvas);
- });
- }
-
static void recordNode(RenderNode& node,
std::function<void(TestCanvas&)> contentCallback) {
TestCanvas canvas(node.stagingProperties().getWidth(),
@@ -164,6 +148,13 @@ public:
node.setStagingDisplayList(canvas.finishRecording());
}
+ /**
+ * Forces a sync of a tree of RenderNode, such that every descendant will have its staging
+ * properties and DisplayList moved to the render copies.
+ *
+ * Note: does not check dirtiness bits, so any non-staging DisplayLists will be discarded.
+ * For this reason, this should generally only be called once on a tree.
+ */
static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) {
syncHierarchyPropertiesAndDisplayListImpl(node.get());
}
@@ -197,7 +188,7 @@ public:
static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
static void drawTextToCanvas(TestCanvas* canvas, const char* text,
- const SkPaint& inPaint, float x, float y);
+ const SkPaint& paint, float x, float y);
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
diff --git a/packages/Shell/Android.mk b/packages/Shell/Android.mk
index 5bd48c63433c..f8c13d63327f 100644
--- a/packages/Shell/Android.mk
+++ b/packages/Shell/Android.mk
@@ -3,7 +3,7 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
@@ -12,3 +12,5 @@ LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_PACKAGE)
+
+include $(LOCAL_PATH)/tests/Android.mk
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 8c39ee654e6d..bf3982d9a71d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
- * Copyright (c) 2014 Google Inc.
+ * 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.
@@ -147,5 +147,9 @@
<action android:name="android.intent.action.BUGREPORT_FINISHED" />
</intent-filter>
</receiver>
+
+ <service
+ android:name=".BugreportProgressService"
+ android:exported="false"/>
</application>
</manifest>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
new file mode 100644
index 000000000000..a2030ef68817
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -0,0 +1,284 @@
+/*
+ * 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.shell;
+
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.getWarningState;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import libcore.io.Streams;
+
+import com.google.android.collect.Lists;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.support.v4.content.FileProvider;
+import android.util.Log;
+import android.util.Patterns;
+import android.widget.Toast;
+
+public class BugreportProgressService extends Service {
+ private static final String TAG = "Shell";
+
+ private static final String AUTHORITY = "com.android.shell";
+
+ static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
+ static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null) {
+ onBugreportFinished(intent);
+ }
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private void onBugreportFinished(Intent intent) {
+ final Context context = getApplicationContext();
+ final Configuration conf = context.getResources().getConfiguration();
+ final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+ final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+ if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
+ triggerLocalNotification(context, bugreportFile, screenshotFile);
+ }
+ stopSelf();
+ }
+
+ /**
+ * Responsible for triggering a notification that allows the user to start a
+ * "share" intent with the bug report. On watches we have other methods to allow the user to
+ * start this intent (usually by triggering it on another connected device); we don't need to
+ * display the notification in this case.
+ */
+ private static void triggerLocalNotification(final Context context, final File bugreportFile,
+ final File screenshotFile) {
+ if (!bugreportFile.exists() || !bugreportFile.canRead()) {
+ Log.e(TAG, "Could not read bugreport file " + bugreportFile);
+ Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
+ if (!isPlainText) {
+ // Already zipped, send it right away.
+ sendBugreportNotification(context, bugreportFile, screenshotFile);
+ } else {
+ // Asynchronously zip the file first, then send it.
+ sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
+ }
+ }
+
+ private static Intent buildWarningIntent(Context context, Intent sendIntent) {
+ final Intent intent = new Intent(context, BugreportWarningActivity.class);
+ intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
+ return intent;
+ }
+
+ /**
+ * Build {@link Intent} that can be used to share the given bugreport.
+ */
+ private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
+ final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ final String mimeType = "application/vnd.android.bugreport";
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setType(mimeType);
+
+ intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+
+ // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
+ // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
+ // create the ClipData object with the attachments URIs.
+ String messageBody = String.format("Build info: %s\nSerial number:%s",
+ SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
+ intent.putExtra(Intent.EXTRA_TEXT, messageBody);
+ final ClipData clipData = new ClipData(null, new String[] { mimeType },
+ new ClipData.Item(null, null, null, bugreportUri));
+ final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
+ if (screenshotUri != null) {
+ clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+ attachments.add(screenshotUri);
+ }
+ intent.setClipData(clipData);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
+
+ final Account sendToAccount = findSendToAccount(context);
+ if (sendToAccount != null) {
+ intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
+ }
+
+ return intent;
+ }
+
+ /**
+ * Sends a bugreport notitication.
+ */
+ private static void sendBugreportNotification(Context context, File bugreportFile,
+ File screenshotFile) {
+ // Files are kept on private storage, so turn into Uris that we can
+ // grant temporary permissions for.
+ final Uri bugreportUri = getUri(context, bugreportFile);
+ final Uri screenshotUri = getUri(context, screenshotFile);
+
+ Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
+ Intent notifIntent;
+
+ // Send through warning dialog by default
+ if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
+ notifIntent = buildWarningIntent(context, sendIntent);
+ } else {
+ notifIntent = sendIntent;
+ }
+ notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final Notification.Builder builder = new Notification.Builder(context)
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setContentTitle(context.getString(R.string.bugreport_finished_title))
+ .setTicker(context.getString(R.string.bugreport_finished_title))
+ .setContentText(context.getString(R.string.bugreport_finished_text))
+ .setContentIntent(PendingIntent.getActivity(
+ context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
+ .setAutoCancel(true)
+ .setLocalOnly(true)
+ .setColor(context.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+
+ NotificationManager.from(context).notify(TAG, 0, builder.build());
+ }
+
+ /**
+ * Sends a zipped bugreport notification.
+ */
+ private static void sendZippedBugreportNotification(final Context context,
+ final File bugreportFile, final File screenshotFile) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ File zippedFile = zipBugreport(bugreportFile);
+ sendBugreportNotification(context, zippedFile, screenshotFile);
+ return null;
+ }
+ }.execute();
+ }
+
+ /**
+ * Zips a bugreport file, returning the path to the new file (or to the
+ * original in case of failure).
+ */
+ private static File zipBugreport(File bugreportFile) {
+ String bugreportPath = bugreportFile.getAbsolutePath();
+ String zippedPath = bugreportPath.replace(".txt", ".zip");
+ Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
+ File bugreportZippedFile = new File(zippedPath);
+ try (InputStream is = new FileInputStream(bugreportFile);
+ ZipOutputStream zos = new ZipOutputStream(
+ new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
+ ZipEntry entry = new ZipEntry(bugreportFile.getName());
+ entry.setTime(bugreportFile.lastModified());
+ zos.putNextEntry(entry);
+ int totalBytes = Streams.copy(is, zos);
+ Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes");
+ zos.closeEntry();
+ // Delete old file;
+ boolean deleted = bugreportFile.delete();
+ if (deleted) {
+ Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
+ } else {
+ Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
+ }
+ return bugreportZippedFile;
+ } catch (IOException e) {
+ Log.e(TAG, "exception zipping file " + zippedPath, e);
+ return bugreportFile; // Return original.
+ }
+ }
+
+ /**
+ * Find the best matching {@link Account} based on build properties.
+ */
+ private static Account findSendToAccount(Context context) {
+ final AccountManager am = (AccountManager) context.getSystemService(
+ Context.ACCOUNT_SERVICE);
+
+ String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
+ if (!preferredDomain.startsWith("@")) {
+ preferredDomain = "@" + preferredDomain;
+ }
+
+ final Account[] accounts = am.getAccounts();
+ Account foundAccount = null;
+ for (Account account : accounts) {
+ if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+ if (!preferredDomain.isEmpty()) {
+ // if we have a preferred domain and it matches, return; otherwise keep
+ // looking
+ if (account.name.endsWith(preferredDomain)) {
+ return account;
+ } else {
+ foundAccount = account;
+ }
+ // if we don't have a preferred domain, just return since it looks like
+ // an email address
+ } else {
+ return account;
+ }
+ }
+ }
+ return foundAccount;
+ }
+
+ private static Uri getUri(Context context, File file) {
+ return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
+ }
+
+ static File getFileExtra(Intent intent, String key) {
+ final String path = intent.getStringExtra(key);
+ if (path != null) {
+ return new File(path);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index c26437285050..f1da14d57c24 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -16,53 +16,23 @@
package com.android.shell;
-import static com.android.shell.BugreportPrefs.STATE_SHOW;
-import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.getFileExtra;
+
+import java.io.File;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.FileUtils;
-import android.os.SystemProperties;
-import android.support.v4.content.FileProvider;
import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Patterns;
-import android.widget.Toast;
-
-import com.google.android.collect.Lists;
-import libcore.io.Streams;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import java.util.ArrayList;
/**
* Receiver that handles finished bugreports, usually by attaching them to an
- * {@link Intent#ACTION_SEND}.
+ * {@link Intent#ACTION_SEND_MULTIPLE}.
*/
public class BugreportReceiver extends BroadcastReceiver {
- private static final String TAG = "Shell";
-
- private static final String AUTHORITY = "com.android.shell";
-
- private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
- private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
/**
* Always keep the newest 8 bugreport files; 4 reports and 4 screenshots are
@@ -77,15 +47,17 @@ public class BugreportReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- final Configuration conf = context.getResources().getConfiguration();
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+ // Clean up older bugreports in background
+ cleanupOldFiles(intent);
- if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
- triggerLocalNotification(context, bugreportFile, screenshotFile);
- }
+ // Delegate to service.
+ Intent serviceIntent = new Intent(context, BugreportProgressService.class);
+ serviceIntent.putExtras(intent.getExtras());
+ context.startService(serviceIntent);
+ }
- // Clean up older bugreports in background
+ private void cleanupOldFiles(Intent intent) {
+ final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
final PendingResult result = goAsync();
new AsyncTask<Void, Void, Void>() {
@Override
@@ -97,201 +69,4 @@ public class BugreportReceiver extends BroadcastReceiver {
}
}.execute();
}
-
- /**
- * Responsible for triggering a notification that allows the user to start a
- * "share" intent with the bug report. On watches we have other methods to allow the user to
- * start this intent (usually by triggering it on another connected device); we don't need to
- * display the notification in this case.
- */
- private void triggerLocalNotification(final Context context, final File bugreportFile,
- final File screenshotFile) {
- if (!bugreportFile.exists() || !bugreportFile.canRead()) {
- Log.e(TAG, "Could not read bugreport file " + bugreportFile);
- Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
- Toast.LENGTH_LONG).show();
- return;
- }
-
- boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
- if (!isPlainText) {
- // Already zipped, send it right away.
- sendBugreportNotification(context, bugreportFile, screenshotFile);
- } else {
- // Asynchronously zip the file first, then send it.
- sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
- }
- }
-
- private static Intent buildWarningIntent(Context context, Intent sendIntent) {
- final Intent intent = new Intent(context, BugreportWarningActivity.class);
- intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
- return intent;
- }
-
- /**
- * Build {@link Intent} that can be used to share the given bugreport.
- */
- private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
- final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
- final String mimeType = "application/vnd.android.bugreport";
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.setType(mimeType);
-
- intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
-
- // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
- // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
- // create the ClipData object with the attachments URIs.
- String messageBody = String.format("Build info: %s\nSerial number:%s",
- SystemProperties.get("ro.build.description"), SystemProperties.get("ro.serialno"));
- intent.putExtra(Intent.EXTRA_TEXT, messageBody);
- final ClipData clipData = new ClipData(null, new String[] { mimeType },
- new ClipData.Item(null, null, null, bugreportUri));
- final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
- if (screenshotUri != null) {
- clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
- attachments.add(screenshotUri);
- }
- intent.setClipData(clipData);
- intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
-
- final Account sendToAccount = findSendToAccount(context);
- if (sendToAccount != null) {
- intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
- }
-
- return intent;
- }
-
- /**
- * Sends a bugreport notitication.
- */
- private static void sendBugreportNotification(Context context, File bugreportFile,
- File screenshotFile) {
- // Files are kept on private storage, so turn into Uris that we can
- // grant temporary permissions for.
- final Uri bugreportUri = getUri(context, bugreportFile);
- final Uri screenshotUri = getUri(context, screenshotFile);
-
- Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
- Intent notifIntent;
-
- // Send through warning dialog by default
- if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
- notifIntent = buildWarningIntent(context, sendIntent);
- } else {
- notifIntent = sendIntent;
- }
- notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- final Notification.Builder builder = new Notification.Builder(context)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setContentTitle(context.getString(R.string.bugreport_finished_title))
- .setTicker(context.getString(R.string.bugreport_finished_title))
- .setContentText(context.getString(R.string.bugreport_finished_text))
- .setContentIntent(PendingIntent.getActivity(
- context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
- .setAutoCancel(true)
- .setLocalOnly(true)
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color));
-
- NotificationManager.from(context).notify(TAG, 0, builder.build());
- }
-
- /**
- * Sends a zipped bugreport notification.
- */
- private static void sendZippedBugreportNotification(final Context context,
- final File bugreportFile, final File screenshotFile) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- File zippedFile = zipBugreport(bugreportFile);
- sendBugreportNotification(context, zippedFile, screenshotFile);
- return null;
- }
- }.execute();
- }
-
- /**
- * Zips a bugreport file, returning the path to the new file (or to the
- * original in case of failure).
- */
- private static File zipBugreport(File bugreportFile) {
- String bugreportPath = bugreportFile.getAbsolutePath();
- String zippedPath = bugreportPath.replace(".txt", ".zip");
- Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
- File bugreportZippedFile = new File(zippedPath);
- try (InputStream is = new FileInputStream(bugreportFile);
- ZipOutputStream zos = new ZipOutputStream(
- new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
- ZipEntry entry = new ZipEntry(bugreportFile.getName());
- entry.setTime(bugreportFile.lastModified());
- zos.putNextEntry(entry);
- int totalBytes = Streams.copy(is, zos);
- Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes");
- zos.closeEntry();
- // Delete old file;
- boolean deleted = bugreportFile.delete();
- if (deleted) {
- Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
- } else {
- Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
- }
- return bugreportZippedFile;
- } catch (IOException e) {
- Log.e(TAG, "exception zipping file " + zippedPath, e);
- return bugreportFile; // Return original.
- }
- }
-
- /**
- * Find the best matching {@link Account} based on build properties.
- */
- private static Account findSendToAccount(Context context) {
- final AccountManager am = (AccountManager) context.getSystemService(
- Context.ACCOUNT_SERVICE);
-
- String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
- if (!preferredDomain.startsWith("@")) {
- preferredDomain = "@" + preferredDomain;
- }
-
- final Account[] accounts = am.getAccounts();
- Account foundAccount = null;
- for (Account account : accounts) {
- if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
- if (!preferredDomain.isEmpty()) {
- // if we have a preferred domain and it matches, return; otherwise keep
- // looking
- if (account.name.endsWith(preferredDomain)) {
- return account;
- } else {
- foundAccount = account;
- }
- // if we don't have a preferred domain, just return since it looks like
- // an email address
- } else {
- return account;
- }
- }
- }
- return foundAccount;
- }
-
- private static Uri getUri(Context context, File file) {
- return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
- }
-
- private static File getFileExtra(Intent intent, String key) {
- final String path = intent.getStringExtra(key);
- if (path != null) {
- return new File(path);
- } else {
- return null;
- }
- }
}
diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk
new file mode 100644
index 000000000000..62a37bc70f4e
--- /dev/null
+++ b/packages/Shell/tests/Android.mk
@@ -0,0 +1,20 @@
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# TODO: update and/or remove
+LOCAL_STATIC_JAVA_LIBRARIES := ub-uiautomator
+#LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target ub-uiautomator
+
+LOCAL_PACKAGE_NAME := ShellTests
+LOCAL_INSTRUMENTATION_FOR := Shell
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/packages/Shell/tests/AndroidManifest.xml b/packages/Shell/tests/AndroidManifest.xml
new file mode 100644
index 000000000000..54b0802e1504
--- /dev/null
+++ b/packages/Shell/tests/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.shell.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="com.android.shell.ActionSendMultipleConsumerActivity"
+ android:label="ActionSendMultipleConsumer"
+ android:theme="@android:style/Theme.NoDisplay"
+ android:noHistory="true"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.intent.action.SEND_MULTIPLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="*/*" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.shell"
+ android:label="Tests for Shell" />
+
+</manifest>
diff --git a/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
new file mode 100644
index 000000000000..e3e99b0d5711
--- /dev/null
+++ b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
@@ -0,0 +1,126 @@
+/*
+ * 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.shell;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+/**
+ * Activity responsible for handling ACTION_SEND_MULTIPLE intents and passing them back to the test
+ * case class (through a {@link CustomActionSendMultipleListener}).
+ */
+public class ActionSendMultipleConsumerActivity extends Activity {
+
+ private static final String CUSTOM_ACTION_SEND_MULTIPLE_INTENT =
+ "com.android.shell.tests.CUSTOM_ACTION_SEND_MULTIPLE";
+
+ private static CustomActionSendMultipleListener sListener;
+
+ static final String UI_NAME = "ActionSendMultipleConsumer";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // The original intent cannot be broadcasted, it will fail due to security violations.
+ // Since the test case is only interested in the extras, we need to create a new custom
+ // intent with just them.
+ final Intent intent = getIntent();
+ final Intent customIntent = new Intent(CUSTOM_ACTION_SEND_MULTIPLE_INTENT);
+ customIntent.putExtras(intent.getExtras());
+
+ getApplicationContext().sendBroadcast(customIntent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ /*
+ * TODO: if finish() is not called, app will crash with an exception such as:
+ * AndroidRuntime: java.lang.RuntimeException: Unable to resume activity
+ * {com.android.shell.tests/com.android.shell.SendMultipleActivity}:
+ * java.lang.IllegalStateException: Activity
+ * {com.android.shell.tests/com.android.shell.SendMultipleActivity} did not call finish()
+ * prior to onResume() completing. That seems to be a problem on M:
+ * https://code.google.com/p/android-developer-preview/issues/detail?id=2353
+ */
+ finish();
+ }
+
+ /**
+ * Gets the {@link CustomActionSendMultipleListener} singleton.
+ */
+ static CustomActionSendMultipleListener getListener(Context context) {
+ synchronized (ActionSendMultipleConsumerActivity.class) {
+ if (sListener == null) {
+ sListener = new CustomActionSendMultipleListener(context);
+ }
+ }
+ return sListener;
+ }
+
+ /**
+ * Listener of custom ACTION_SEND_MULTIPLE_INTENTS.
+ */
+ static class CustomActionSendMultipleListener {
+
+ private static final int TIMEOUT = 10;
+ private final BlockingQueue<Bundle> mQueue = new SynchronousQueue<>();
+
+ public CustomActionSendMultipleListener(Context context) {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ mQueue.put(intent.getExtras());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ };
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(CUSTOM_ACTION_SEND_MULTIPLE_INTENT);
+ context.registerReceiver(receiver, filter);
+ }
+
+ /**
+ * Gets the extras from the custom intent, blocking until it's received.
+ */
+ Bundle getExtras() {
+ Bundle bundle = null;
+ try {
+ bundle = mQueue.poll(TIMEOUT, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ if (bundle == null) {
+ throw new IllegalStateException("Intent not received after " + TIMEOUT + "s");
+ }
+ return bundle;
+ }
+ }
+}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
new file mode 100644
index 000000000000..1bdd9ddc874d
--- /dev/null
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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.shell;
+
+import static android.test.MoreAsserts.assertContainsRegex;
+import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
+import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import libcore.io.Streams;
+import android.app.Instrumentation;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.service.notification.StatusBarNotification;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.shell.ActionSendMultipleConsumerActivity.CustomActionSendMultipleListener;
+
+/**
+ * Integration tests for {@link BugreportReceiver}.
+ * <p>
+ * These tests don't mock any component and rely on external UI components (like the notification
+ * bar and activity chooser), which can make them unreliable and slow.
+ * <p>
+ * The general workflow is:
+ * <ul>
+ * <li>creates the bug report files
+ * <li>generates the BUGREPORT_FINISHED intent
+ * <li>emulate user actions to share the intent with a custom activity
+ * <li>asserts the extras received by the custom activity
+ * </ul>
+ * <p>
+ * TODO: currently, these tests only work if the bug report sharing warning is disabled and the
+ * device screen is unlocked.
+ */
+public class BugreportReceiverTest extends InstrumentationTestCase {
+
+ private static final String TAG = "BugreportReceiverTest";
+
+ // Timeout for UI operations, in milliseconds.
+ private static final int TIMEOUT = 1000;
+
+ private static final String ROOT_DIR = "/data/data/com.android.shell/files/bugreports";
+ private static final String BUGREPORT_FILE = "test_bugreport.txt";
+ private static final String ZIP_FILE = "test_bugreport.zip";
+ private static final String PLAIN_TEXT_PATH = ROOT_DIR + "/" + BUGREPORT_FILE;
+ private static final String ZIP_PATH = ROOT_DIR + "/" + ZIP_FILE;
+ private static final String SCREENSHOT_PATH = ROOT_DIR + "/test_screenshot.png";
+
+ private static final String BUGREPORT_CONTENT = "Dump, might as well dump!\n";
+ private static final String SCREENSHOT_CONTENT = "A picture is worth a thousand words!\n";
+
+ private Context mContext;
+ private UiBot mUiBot;
+ private CustomActionSendMultipleListener mListener;
+
+ @Override
+ protected void setUp() throws Exception {
+ Instrumentation instrumentation = getInstrumentation();
+ mContext = instrumentation.getTargetContext();
+ mUiBot = new UiBot(UiDevice.getInstance(instrumentation), TIMEOUT);
+ mListener = ActionSendMultipleConsumerActivity.getListener(mContext);
+ cancelExistingNotifications();
+ }
+
+ public void testBugreportFinished_plainBugreportAndScreenshot() throws Exception {
+ createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
+ createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
+ Bundle extras = sendBugreportFinishedIntent(PLAIN_TEXT_PATH, SCREENSHOT_PATH);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+ }
+
+ public void testBugreportFinished_zippedBugreportAndScreenshot() throws Exception {
+ createZipFile(ZIP_PATH, BUGREPORT_FILE, BUGREPORT_CONTENT);
+ createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
+ Bundle extras = sendBugreportFinishedIntent(ZIP_PATH, SCREENSHOT_PATH);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+ }
+
+ public void testBugreportFinished_plainBugreportAndNoScreenshot() throws Exception {
+ createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
+ Bundle extras = sendBugreportFinishedIntent(PLAIN_TEXT_PATH, null);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, null);
+ }
+
+ public void testBugreportFinished_zippedBugreportAndNoScreenshot() throws Exception {
+ createZipFile(ZIP_PATH, BUGREPORT_FILE, BUGREPORT_CONTENT);
+ Bundle extras = sendBugreportFinishedIntent(ZIP_PATH, null);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, null);
+ }
+
+ private void cancelExistingNotifications() {
+ NotificationManager nm = NotificationManager.from(mContext);
+ for (StatusBarNotification notification : nm.getActiveNotifications()) {
+ int id = notification.getId();
+ Log.i(TAG, "Canceling existing notification (id=" + id + ")");
+ nm.cancel(id);
+ }
+ }
+
+ /**
+ * Sends a "bugreport finished" intent and waits for the result.
+ *
+ * @return extras sent to the bugreport finished consumer.
+ */
+ private Bundle sendBugreportFinishedIntent(String bugreportPath, String screenshotPath) {
+ Intent intent = new Intent("android.intent.action.BUGREPORT_FINISHED");
+ if (bugreportPath != null) {
+ intent.putExtra(EXTRA_BUGREPORT, bugreportPath);
+ }
+ if (screenshotPath != null) {
+ intent.putExtra(EXTRA_SCREENSHOT, screenshotPath);
+ }
+
+ mContext.sendBroadcast(intent);
+
+ mUiBot.clickOnNotification(mContext.getString(R.string.bugreport_finished_title));
+ mUiBot.chooseActivity(UI_NAME);
+ return mListener.getExtras();
+ }
+
+ /**
+ * Asserts the proper ACTION_SEND_MULTIPLE intent was sent.
+ */
+ private void assertActionSendMultiple(Bundle extras, String bugreportContent,
+ String screenshotContent) throws IOException {
+ String body = extras.getString(Intent.EXTRA_TEXT);
+ assertContainsRegex("missing build info",
+ SystemProperties.get("ro.build.description"), body);
+ assertContainsRegex("missing serial number",
+ SystemProperties.get("ro.serialno"), body);
+
+ assertEquals("wrong subject", ZIP_FILE, extras.getString(Intent.EXTRA_SUBJECT));
+
+ List<Uri> attachments = extras.getParcelableArrayList(Intent.EXTRA_STREAM);
+ int expectedSize = screenshotContent != null ? 2 : 1;
+ assertEquals("wrong number of attachments", expectedSize, attachments.size());
+
+ // Need to interact through all attachments, since order is not guaranteed.
+ Uri zipUri = null, screenshotUri = null;
+ for (Uri attachment : attachments) {
+ if (attachment.getPath().endsWith(".zip")) {
+ zipUri = attachment;
+ }
+ if (attachment.getPath().endsWith(".png")) {
+ screenshotUri = attachment;
+ }
+ }
+ assertNotNull("did not get .zip attachment", zipUri);
+ assertZipContent(zipUri, BUGREPORT_FILE, BUGREPORT_CONTENT);
+
+ if (screenshotContent != null) {
+ assertNotNull("did not get .png attachment", screenshotUri);
+ assertContent(screenshotUri, SCREENSHOT_CONTENT);
+ } else {
+ assertNull("should not have .png attachment", screenshotUri);
+ }
+ }
+
+ private void assertContent(Uri uri, String expectedContent) throws IOException {
+ Log.v(TAG, "assertContents(uri=" + uri);
+ try (InputStream is = mContext.getContentResolver().openInputStream(uri)) {
+ String actualContent = new String(Streams.readFully(is));
+ assertEquals("wrong content for '" + uri + "'", expectedContent, actualContent);
+ }
+ }
+
+ private void assertZipContent(Uri uri, String entryName, String expectedContent)
+ throws IOException, IOException {
+ Log.v(TAG, "assertZipEntry(uri=" + uri + ", entryName=" + entryName);
+ try (ZipInputStream zis = new ZipInputStream(mContext.getContentResolver().openInputStream(
+ uri))) {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ Log.v(TAG, "Zip entry: " + entry.getName());
+ if (entry.getName().equals(entryName)) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ Streams.copy(zis, bos);
+ String actualContent = new String(bos.toByteArray(), "UTF-8");
+ bos.close();
+ assertEquals("wrong content for zip entry'" + entryName + "' on '" + uri + "'",
+ expectedContent, actualContent);
+ return;
+ }
+ }
+ }
+ fail("Did not find entry '" + entryName + "' on file '" + uri + "'");
+ }
+
+ private static void createTextFile(String path, String content) throws IOException {
+ Log.v(TAG, "createFile(" + path + ")");
+ try (Writer writer = new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(path)))) {
+ writer.write(content);
+ }
+ }
+
+ private void createZipFile(String path, String entryName, String content) throws IOException {
+ Log.v(TAG, "createZipFile(" + path + ", " + entryName + ")");
+ try (ZipOutputStream zos = new ZipOutputStream(
+ new BufferedOutputStream(new FileOutputStream(path)))) {
+ ZipEntry entry = new ZipEntry(entryName);
+ zos.putNextEntry(entry);
+ byte[] data = content.getBytes();
+ zos.write(data, 0, data.length);
+ zos.closeEntry();
+ }
+ }
+}
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
new file mode 100644
index 000000000000..f5dd31c6fdc0
--- /dev/null
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * A helper class for UI-related testing tasks.
+ */
+final class UiBot {
+
+ private static final String TAG = "UiBot";
+ private static final String SYSTEMUI_PACKAGED = "com.android.systemui";
+
+ private final UiDevice mDevice;
+ private final int mTimeout;
+
+ public UiBot(UiDevice device, int timeout) {
+ mDevice = device;
+ mTimeout = timeout;
+ }
+
+ /**
+ * Opens the system notification and clicks a given notification.
+ *
+ * @param text Notificaton's text as displayed by the UI.
+ */
+ public void clickOnNotification(String text) {
+ boolean opened = mDevice.openNotification();
+ Log.v(TAG, "openNotification(): " + opened);
+ boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGED)), mTimeout);
+ assertTrue("could not get system ui (" + SYSTEMUI_PACKAGED + ")", gotIt);
+
+ gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
+ assertTrue("object with text '(" + text + "') not visible yet", gotIt);
+
+ UiObject notification = getVisibleObject(text);
+
+ click(notification, "bug report notification");
+ }
+
+ /**
+ * Gets an object which is guaranteed to be present in the current UI.\
+ *
+ * @param text Object's text as displayed by the UI.
+ */
+ public UiObject getVisibleObject(String text) {
+ UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
+ assertTrue("could not find object with text '(" + text + "')", uiObject.exists());
+ return uiObject;
+ }
+
+ /**
+ * Clicks on a UI element.
+ *
+ * @param uiObject UI element to be clicked.
+ * @param description Elements's description used on logging statements.
+ */
+ public void click(UiObject uiObject, String description) {
+ try {
+ boolean clicked = uiObject.click();
+ // TODO: assertion below fails sometimes, even though the click succeeded,
+ // (specially when clicking the "Just Once" button), so it's currently just logged.
+ // assertTrue("could not click on object '" + description + "'", clicked);
+
+ Log.v(TAG, "onClick for " + description + ": " + clicked);
+ } catch (UiObjectNotFoundException e) {
+ throw new IllegalStateException("exception when clicking on object '" + description
+ + "'", e);
+ }
+ }
+
+ /**
+ * Chooses a given activity to handle an Intent, using the "Just Once" button.
+ *
+ * @param name name of the activity as displayed in the UI (typically the value set by
+ * {@code android:label} in the manifest).
+ */
+ // TODO: UI Automator should provide such logic.
+ public void chooseActivity(String name) {
+ // First select activity if it's not the default option.
+ boolean gotIt = mDevice.wait(Until.hasObject(By.text(name)), mTimeout);
+ // TODO: if the activity is indeed the default option, call above will timeout, which will
+ // make the tests run slower. It might be better to change the logic to assume the default
+ // first.
+ if (gotIt) {
+ Log.v(TAG, "Found activity " + name + ", it's not default action");
+ UiObject activityChooser = getVisibleObject(name);
+ click(activityChooser, "activity chooser");
+ } else {
+ String text = String.format("Share with %s", name);
+ Log.v(TAG, "Didn't find activity " + name
+ + ", assuming it's the default action and search for '" + text + "'");
+ gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
+ assertTrue("did not find text '" + text + "'", gotIt);
+ }
+
+ // Then clicks the "Just Once" button.
+ gotIt = mDevice
+ .wait(Until.hasObject(By.res("android", "button_once")), mTimeout);
+ assertTrue("'Just Once' button not visible yet", gotIt);
+
+ UiObject justOnce = mDevice
+ .findObject(new UiSelector().resourceId("android:id/button_once"));
+ assertTrue("'Just Once' button not found", justOnce.exists());
+
+ click(justOnce, "Just Once");
+ }
+}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 5e4f2b2be258..78a4e359ad24 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1225,7 +1225,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
void unbindCurrentClientLocked(
/* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
if (mCurClient != null) {
- if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
+ if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client="
+ mCurClient.client.asBinder());
if (mBoundToMethod) {
mBoundToMethod = false;
@@ -1290,8 +1290,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
}
- InputBindResult startInputLocked(IInputMethodClient client,
- IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+ InputBindResult startInputLocked(
+ /* @InputMethodClient.StartInputReason */ final int startInputReason,
+ IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
+ int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
@@ -1320,8 +1322,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
}
- InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
- IInputContext inputContext, @NonNull EditorInfo attribute, int controlFlags) {
+ InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
+ @NonNull EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
@@ -1340,7 +1342,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// If the client is changing, we need to switch over to the new
// one.
unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
- if (DEBUG) Slog.v(TAG, "switching to client: client = "
+ if (DEBUG) Slog.v(TAG, "switching to client: client="
+ cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
// If the screen is on, inform the new client it is active
@@ -1442,15 +1444,26 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
@Override
- public InputBindResult startInput(IInputMethodClient client,
- IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+ public InputBindResult startInput(
+ /* @InputMethodClient.StartInputReason */ final int startInputReason,
+ IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
+ int controlFlags) {
if (!calledFromValidUser()) {
return null;
}
synchronized (mMethodMap) {
+ if (DEBUG) {
+ Slog.v(TAG, "startInput: reason="
+ + InputMethodClient.getStartInputReason(startInputReason)
+ + " client = " + client.asBinder()
+ + " inputContext=" + inputContext
+ + " attribute=" + attribute
+ + " controlFlags=#" + Integer.toHexString(controlFlags));
+ }
final long ident = Binder.clearCallingIdentity();
try {
- return startInputLocked(client, inputContext, attribute, controlFlags);
+ return startInputLocked(startInputReason, client, inputContext, attribute,
+ controlFlags);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2151,17 +2164,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
@Override
- public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
- int controlFlags, int softInputMode, int windowFlags,
- EditorInfo attribute, IInputContext inputContext) {
+ public InputBindResult windowGainedFocus(
+ /* @InputMethodClient.StartInputReason */ final int startInputReason,
+ IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
+ int windowFlags, EditorInfo attribute, IInputContext inputContext) {
// Needs to check the validity before clearing calling identity
final boolean calledFromValidUser = calledFromValidUser();
-
InputBindResult res = null;
long ident = Binder.clearCallingIdentity();
try {
synchronized (mMethodMap) {
- if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
+ if (DEBUG) Slog.v(TAG, "windowGainedFocus: reason="
+ + InputMethodClient.getStartInputReason(startInputReason)
+ + " client=" + client.asBinder()
+ + " inputContext=" + inputContext
+ + " attribute=" + attribute
+ " controlFlags=#" + Integer.toHexString(controlFlags)
+ " softInputMode=#" + Integer.toHexString(softInputMode)
+ " windowFlags=#" + Integer.toHexString(windowFlags));
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ff829ff321d5..424b90234ca2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -24,7 +24,6 @@ import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -216,14 +215,15 @@ public class UserManagerService extends IUserManager.Stub {
private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
/**
- * User restrictions set by {@link DevicePolicyManager} that should be applied to all users,
- * including guests.
+ * User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
+ * that should be applied to all users, including guests.
*/
@GuardedBy("mRestrictionsLock")
private Bundle mDevicePolicyGlobalUserRestrictions;
/**
- * User restrictions set by {@link DevicePolicyManager} for each user.
+ * User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
+ * for each user.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
@@ -248,6 +248,12 @@ public class UserManagerService extends IUserManager.Stub {
private final LocalService mLocalService;
+ @GuardedBy("mUsersLock")
+ private boolean mIsDeviceManaged;
+
+ @GuardedBy("mUsersLock")
+ private final SparseBooleanArray mIsUserManaged = new SparseBooleanArray();
+
@GuardedBy("mUserRestrictionsListeners")
private final ArrayList<UserRestrictionsListener> mUserRestrictionsListeners =
new ArrayList<>();
@@ -509,11 +515,9 @@ public class UserManagerService extends IUserManager.Stub {
if (!userInfo.isAdmin()) {
return false;
}
+ // restricted profile can be created if there is no DO set and the admin user has no PO;
+ return !mIsDeviceManaged && !mIsUserManaged.get(userId);
}
- DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- // restricted profile can be created if there is no DO set and the admin user has no PO
- return !dpm.isDeviceManaged() && dpm.getProfileOwnerAsUser(userId) == null;
}
/*
@@ -1596,11 +1600,10 @@ public class UserManagerService extends IUserManager.Stub {
if (UserManager.isSplitSystemUser()
&& !isGuest && !isManagedProfile && getPrimaryUser() == null) {
flags |= UserInfo.FLAG_PRIMARY;
- DevicePolicyManager devicePolicyManager = (DevicePolicyManager)
- mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- if (devicePolicyManager == null
- || !devicePolicyManager.isDeviceManaged()) {
- flags |= UserInfo.FLAG_ADMIN;
+ synchronized (mUsersLock) {
+ if (!mIsDeviceManaged) {
+ flags |= UserInfo.FLAG_ADMIN;
+ }
}
}
userId = getNextAvailableId();
@@ -1865,6 +1868,13 @@ public class UserManagerService extends IUserManager.Stub {
// Remove this user from the list
synchronized (mUsersLock) {
mUsers.remove(userHandle);
+ mIsUserManaged.delete(userHandle);
+ }
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.remove(userHandle);
+ mAppliedUserRestrictions.remove(userHandle);
+ mCachedEffectiveUserRestrictions.remove(userHandle);
+ mDevicePolicyLocalUserRestrictions.remove(userHandle);
}
// Remove user file
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
@@ -2376,9 +2386,10 @@ public class UserManagerService extends IUserManager.Stub {
if (user == null) {
continue;
}
+ final int userId = user.id;
pw.print(" "); pw.print(user);
pw.print(" serialNo="); pw.print(user.serialNumber);
- if (mRemovingUserIds.get(mUsers.keyAt(i))) {
+ if (mRemovingUserIds.get(userId)) {
pw.print(" <removing> ");
}
if (user.partial) {
@@ -2403,6 +2414,8 @@ public class UserManagerService extends IUserManager.Stub {
sb.append(" ago");
pw.println(sb);
}
+ pw.print(" Has profile owner: ");
+ pw.println(mIsUserManaged.get(userId));
pw.println(" Restrictions:");
synchronized (mRestrictionsLock) {
UserRestrictionsUtils.dumpRestrictions(
@@ -2427,6 +2440,10 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mGuestRestrictions) {
UserRestrictionsUtils.dumpRestrictions(pw, " ", mGuestRestrictions);
}
+ synchronized (mUsersLock) {
+ pw.println();
+ pw.println(" Device managed: " + mIsDeviceManaged);
+ }
}
}
@@ -2507,6 +2524,20 @@ public class UserManagerService extends IUserManager.Stub {
mUserRestrictionsListeners.remove(listener);
}
}
+
+ @Override
+ public void setDeviceManaged(boolean isManaged) {
+ synchronized (mUsersLock) {
+ mIsDeviceManaged = isManaged;
+ }
+ }
+
+ @Override
+ public void setUserManaged(int userId, boolean isManaged) {
+ synchronized (mUsersLock) {
+ mIsUserManaged.put(userId, isManaged);
+ }
+ }
}
private class Shell extends ShellCommand {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 21d3bbaea730..ac90daf97b90 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -110,7 +110,6 @@ import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.TypedValue;
@@ -406,7 +405,7 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Windows whose surface should be destroyed.
*/
- private final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
+ final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
/**
* Windows with a preserved surface waiting to be destroyed. These windows
@@ -443,11 +442,11 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState[] mRebuildTmp = new WindowState[20];
/**
- * Stores for each user whether screencapture is disabled for all their windows.
+ * Stores for each user whether screencapture is disabled
* This array is essentially a cache for all userId for
* {@link android.app.admin.DevicePolicyManager#getScreenCaptureDisabled}
*/
- private SparseBooleanArray mScreenCaptureDisabled = new SparseBooleanArray();
+ SparseArray<Boolean> mScreenCaptureDisabled = new SparseArray<>();
IInputMethodManager mInputMethodManager;
@@ -2109,11 +2108,25 @@ public class WindowManagerService extends IWindowManager.Stub
executeAppTransition();
}
+ /**
+ * Returns whether screen capture is disabled for all windows of a specific user.
+ */
+ boolean isScreenCaptureDisabledLocked(int userId) {
+ Boolean disabled = mScreenCaptureDisabled.get(userId);
+ if (disabled == null) {
+ return false;
+ }
+ return disabled;
+ }
+
boolean isSecureLocked(WindowState w) {
- if ((w.mAttrs.flags & FLAG_SECURE) != 0) {
+ if ((w.mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+ return true;
+ }
+ if (isScreenCaptureDisabledLocked(UserHandle.getUserId(w.mOwnerUid))) {
return true;
}
- return mScreenCaptureDisabled.get(UserHandle.getUserId(w.mOwnerUid));
+ return false;
}
/**
@@ -2637,10 +2650,8 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.i(TAG, "Relayout " + win + ": oldVis=" + oldVisibility
+ " newVis=" + viewVisibility, stack);
}
- final AppWindowToken appToken = win.mAppToken;
- final boolean visible = viewVisibility == View.VISIBLE
- && (appToken == null ? win.mPolicyVisibility : !appToken.clientHidden);
- if (visible) {
+ if (viewVisibility == View.VISIBLE &&
+ (win.mAppToken == null || !win.mAppToken.clientHidden)) {
result = relayoutVisibleWindow(outConfig, result, win, winAnimator, attrChanges,
oldVisibility);
try {
@@ -2726,8 +2737,8 @@ public class WindowManagerService extends IWindowManager.Stub
mWallpaperControllerLocked.updateWallpaperOffset(
win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
}
- if (appToken != null) {
- appToken.updateReportedVisibilityLocked();
+ if (win.mAppToken != null) {
+ win.mAppToken.updateReportedVisibilityLocked();
}
if (winAnimator.mReportSurfaceResized) {
winAnimator.mReportSurfaceResized = false;
@@ -10200,25 +10211,6 @@ public class WindowManagerService extends IWindowManager.Stub
mDestroySurface.add(win);
}
- boolean destroySurfacesLocked() {
- boolean wallpaperDestroyed = false;
- for (int i = mDestroySurface.size() - 1; i >= 0; i--) {
- WindowState win = mDestroySurface.get(i);
- win.mDestroying = false;
- if (mInputMethodWindow == win) {
- mInputMethodWindow = null;
- }
- if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
- wallpaperDestroyed = true;
- }
- if (!win.shouldSaveSurface()) {
- win.mWinAnimator.destroySurfaceLocked();
- }
- }
- mDestroySurface.clear();
- return wallpaperDestroyed;
- }
-
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 a0a2162b0f90..064b4128a2ce 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -411,10 +411,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
final private Rect mTmpRect = new Rect();
- // This window often remains added but hidden, so we want to destroy its surface when it's not
- // visible.
- private final boolean mDestroySurfaceWhenHidden;
-
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
@@ -462,7 +458,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mSubLayer = 0;
mInputWindowHandle = null;
mWinAnimator = null;
- mDestroySurfaceWhenHidden = false;
return;
}
mDeathRecipient = deathRecipient;
@@ -561,7 +556,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mInputWindowHandle = new InputWindowHandle(
mAppToken != null ? mAppToken.mInputApplicationHandle : null, this,
displayContent.getDisplayId());
- mDestroySurfaceWhenHidden = mAttrs.type == TYPE_DOCK_DIVIDER;
}
void attach() {
@@ -1319,10 +1313,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
mHasSurface = hasSurface;
}
- boolean shouldDestroySurfaceWhenAnimationFinishes() {
- return mExiting || (mDestroySurfaceWhenHidden && !mPolicyVisibilityAfterAnim);
- }
-
private final class DeadWindowEventReceiver extends InputEventReceiver {
DeadWindowEventReceiver(InputChannel inputChannel) {
super(inputChannel, mService.mH.getLooper());
@@ -1605,11 +1595,6 @@ final class WindowState implements WindowManagerPolicy.WindowState {
// Already showing.
return false;
}
- if (!mHasSurface && mDestroySurfaceWhenHidden) {
- // This is a window that doesn't retain the surface when it's hidden, so immediately
- // when we want to show it again, we need to create the surface for it.
- mWinAnimator.createSurfaceLocked();
- }
if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this);
if (doAnimation) {
if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility="
@@ -1645,7 +1630,8 @@ final class WindowState implements WindowManagerPolicy.WindowState {
doAnimation = false;
}
}
- final boolean current = doAnimation ? mPolicyVisibilityAfterAnim : mPolicyVisibility;
+ boolean current = doAnimation ? mPolicyVisibilityAfterAnim
+ : mPolicyVisibility;
if (!current) {
// Already hiding.
return false;
@@ -1656,9 +1642,11 @@ final class WindowState implements WindowManagerPolicy.WindowState {
doAnimation = false;
}
}
- mPolicyVisibilityAfterAnim = false;
- if (!doAnimation) {
+ if (doAnimation) {
+ mPolicyVisibilityAfterAnim = false;
+ } else {
if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this);
+ mPolicyVisibilityAfterAnim = false;
mPolicyVisibility = false;
// Window is no longer visible -- make sure if we were waiting
// for it to be displayed before enabling the display, that
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 9726034d0c0f..93c2ff608442 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -446,7 +446,7 @@ class WindowStateAnimator {
}
}
- if (!mWin.shouldDestroySurfaceWhenAnimationFinishes()) {
+ if (!mWin.mExiting) {
return;
}
@@ -454,13 +454,12 @@ class WindowStateAnimator {
return;
}
- if (localLOGV) Slog.v(TAG, "Exit animation finished in " + this + ": remove="
- + mWin.mRemoveOnExit);
+ if (WindowManagerService.localLOGV) Slog.v(
+ TAG, "Exit animation finished in " + this
+ + ": remove=" + mWin.mRemoveOnExit);
if (mSurfaceController != null && mSurfaceController.hasSurface()) {
- mService.scheduleSurfaceDestroy(mWin);
- if (mWin.mExiting) {
- mWin.mDestroying = true;
- }
+ mService.mDestroySurface.add(mWin);
+ mWin.mDestroying = true;
hide("finishExit");
}
mWin.mExiting = false;
@@ -646,7 +645,7 @@ class WindowStateAnimator {
return null;
}
- if (localLOGV) {
+ if (WindowManagerService.localLOGV) {
Slog.v(TAG, "Got surface: " + mSurfaceController
+ ", set left=" + w.mFrame.left + " top=" + w.mFrame.top
+ ", animLayer=" + mAnimLayer);
@@ -667,7 +666,7 @@ class WindowStateAnimator {
mAnimLayer);
mLastHidden = true;
- if (localLOGV) Slog.v(
+ if (WindowManagerService.localLOGV) Slog.v(
TAG, "Created surface " + this);
}
return mSurfaceController;
@@ -974,7 +973,7 @@ class WindowStateAnimator {
//Slog.i(TAG, "Not applying alpha transform");
}
- if ((DEBUG_SURFACE_TRACE || localLOGV)
+ if ((DEBUG_SURFACE_TRACE || WindowManagerService.localLOGV)
&& (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+ " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
@@ -995,7 +994,7 @@ class WindowStateAnimator {
return;
}
- if (localLOGV) Slog.v(
+ if (WindowManagerService.localLOGV) Slog.v(
TAG, "computeShownFrameLocked: " + this +
" not attached, mAlpha=" + mAlpha);
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index b3c23d1a8ab0..f8b8d6ced8d8 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -296,8 +296,10 @@ class WindowSurfaceController {
}
boolean showRobustlyInTransaction() {
- if (SHOW_TRANSACTIONS) logSurface("SHOW (performLayout)", null);
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during relayout");
+ if (SHOW_TRANSACTIONS) logSurface(
+ "SHOW (performLayout)", null);
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
+ + " during relayout");
if (mHiddenForCrop) {
return false;
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 54d18e9cf363..214901991fac 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -381,7 +381,25 @@ class WindowSurfacePlacer {
}
// Destroy the surface of any windows that are no longer visible.
- final boolean wallpaperDestroyed = mService.destroySurfacesLocked();
+ boolean wallpaperDestroyed = false;
+ i = mService.mDestroySurface.size();
+ if (i > 0) {
+ do {
+ i--;
+ WindowState win = mService.mDestroySurface.get(i);
+ win.mDestroying = false;
+ if (mService.mInputMethodWindow == win) {
+ mService.mInputMethodWindow = null;
+ }
+ if (mWallpaperControllerLocked.isWallpaperTarget(win)) {
+ wallpaperDestroyed = true;
+ }
+ if (!win.shouldSaveSurface()) {
+ win.mWinAnimator.destroySurfaceLocked();
+ }
+ } while (i > 0);
+ mService.mDestroySurface.clear();
+ }
// Time to remove any exiting tokens?
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f80a61163cdf..819925080ea3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1071,7 +1071,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
Owners newOwners() {
- return new Owners(mContext);
+ return new Owners(mContext, getUserManager(), getUserManagerInternal());
}
UserManager getUserManager() {
@@ -2150,20 +2150,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private void ensureDeviceOwnerUserStarted() {
- if (mOwners.hasDeviceOwner()) {
- final int userId = mOwners.getDeviceOwnerUserId();
- if (VERBOSE_LOG) {
- Log.v(LOG_TAG, "Starting non-system DO user: " + userId);
+ final int userId;
+ synchronized (this) {
+ if (!mOwners.hasDeviceOwner()) {
+ return;
}
- if (userId != UserHandle.USER_SYSTEM) {
- try {
- mInjector.getIActivityManager().startUserInBackground(userId);
+ userId = mOwners.getDeviceOwnerUserId();
+ }
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "Starting non-system DO user: " + userId);
+ }
+ if (userId != UserHandle.USER_SYSTEM) {
+ try {
+ mInjector.getIActivityManager().startUserInBackground(userId);
- // STOPSHIP Prevent the DO user from being killed.
+ // STOPSHIP Prevent the DO user from being killed.
- } catch (RemoteException e) {
- Slog.w(LOG_TAG, "Exception starting user", e);
- }
+ } catch (RemoteException e) {
+ Slog.w(LOG_TAG, "Exception starting user", e);
}
}
}
@@ -4584,7 +4588,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ " for device owner");
}
synchronized (this) {
- enforceCanSetDeviceOwner(userId);
+ enforceCanSetDeviceOwnerLocked(userId);
// Shutting down backup manager service permanently.
long ident = mInjector.binderClearCallingIdentity();
@@ -4751,7 +4755,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ " not installed for userId:" + userHandle);
}
synchronized (this) {
- enforceCanSetProfileOwner(userHandle);
+ enforceCanSetProfileOwnerLocked(userHandle);
mOwners.setProfileOwner(who, ownerName, userHandle);
mOwners.writeProfileOwner(userHandle);
return true;
@@ -4953,7 +4957,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* - SYSTEM_UID
* - adb if there are not accounts.
*/
- private void enforceCanSetProfileOwner(int userHandle) {
+ private void enforceCanSetProfileOwnerLocked(int userHandle) {
UserInfo info = mUserManager.getUserInfo(userHandle);
if (info == null) {
// User doesn't exist.
@@ -4995,7 +4999,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* The device owner can only be set before the setup phase of the primary user has completed,
* except for adb if no accounts or additional users are present on the device.
*/
- private void enforceCanSetDeviceOwner(int userId) {
+ private void enforceCanSetDeviceOwnerLocked(int userId) {
if (mOwners.hasDeviceOwner()) {
throw new IllegalStateException("Trying to set the device owner, but device owner "
+ "is already set.");
@@ -6819,20 +6823,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public boolean isProvisioningAllowed(String action) {
final int callingUserId = mInjector.userHandleGetCallingUserId();
if (DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE.equals(action)) {
- if (mOwners.hasDeviceOwner()) {
- if (!mInjector.userManagerIsSplitSystemUser()) {
- // Only split-system-user systems support managed-profiles in combination with
- // device-owner.
- return false;
- }
- if (mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM) {
- // Only system device-owner supports managed-profiles. Non-system device-owner
- // doesn't.
- return false;
- }
- if (callingUserId == UserHandle.USER_SYSTEM) {
- // Managed-profiles cannot be setup on the system user, only regular users.
- return false;
+ synchronized (this) {
+ if (mOwners.hasDeviceOwner()) {
+ if (!mInjector.userManagerIsSplitSystemUser()) {
+ // Only split-system-user systems support managed-profiles in combination with
+ // device-owner.
+ return false;
+ }
+ if (mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM) {
+ // Only system device-owner supports managed-profiles. Non-system device-owner
+ // doesn't.
+ return false;
+ }
+ if (callingUserId == UserHandle.USER_SYSTEM) {
+ // Managed-profiles cannot be setup on the system user, only regular users.
+ return false;
+ }
}
}
if (getProfileOwner(callingUserId) != null) {
@@ -6877,8 +6883,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private boolean isDeviceOwnerProvisioningAllowed(int callingUserId) {
- if (mOwners.hasDeviceOwner()) {
- return false;
+ synchronized (this) {
+ if (mOwners.hasDeviceOwner()) {
+ return false;
+ }
}
if (getProfileOwner(callingUserId) != null) {
return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 435de7ac3f3d..f7de0b3896f7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -23,6 +23,7 @@ import android.content.pm.UserInfo;
import android.os.Environment;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
@@ -50,6 +51,9 @@ import libcore.io.IoUtils;
/**
* Stores and restores state for the Device and Profile owners. By definition there can be
* only one device owner, but there may be a profile owner for each user.
+ *
+ * <p>This class is not thread safe. (i.e. access to this class must always be synchronized
+ * in the caller side.)
*/
class Owners {
private static final String TAG = "DevicePolicyManagerService";
@@ -78,8 +82,8 @@ class Owners {
private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy";
- private final Context mContext;
private final UserManager mUserManager;
+ private final UserManagerInternal mUserManagerInternal;
// Internal state for the device owner package.
private OwnerInfo mDeviceOwner;
@@ -92,44 +96,48 @@ class Owners {
// Local system update policy controllable by device owner.
private SystemUpdatePolicy mSystemUpdatePolicy;
- public Owners(Context context) {
- mContext = context;
- mUserManager = context.getSystemService(UserManager.class);
+ public Owners(Context context, UserManager userManager,
+ UserManagerInternal userManagerInternal) {
+ mUserManager = userManager;
+ mUserManagerInternal = userManagerInternal;
}
/**
* Load configuration from the disk.
*/
void load() {
- synchronized (this) {
- // First, try to read from the legacy file.
- final File legacy = getLegacyConfigFileWithTestOverride();
+ // First, try to read from the legacy file.
+ final File legacy = getLegacyConfigFileWithTestOverride();
- if (readLegacyOwnerFile(legacy)) {
- if (DEBUG) {
- Log.d(TAG, "Legacy config file found.");
- }
+ final List<UserInfo> users = mUserManager.getUsers();
- // Legacy file exists, write to new files and remove the legacy one.
- writeDeviceOwner();
- for (int userId : getProfileOwnerKeys()) {
- writeProfileOwner(userId);
- }
- if (DEBUG) {
- Log.d(TAG, "Deleting legacy config file");
- }
- if (!legacy.delete()) {
- Slog.e(TAG, "Failed to remove the legacy setting file");
- }
- } else {
- // No legacy file, read from the new format files.
- new DeviceOwnerReadWriter().readFromFileLocked();
+ if (readLegacyOwnerFile(legacy)) {
+ if (DEBUG) {
+ Log.d(TAG, "Legacy config file found.");
+ }
- final List<UserInfo> users = mUserManager.getUsers();
- for (UserInfo ui : users) {
- new ProfileOwnerReadWriter(ui.id).readFromFileLocked();
- }
+ // Legacy file exists, write to new files and remove the legacy one.
+ writeDeviceOwner();
+ for (int userId : getProfileOwnerKeys()) {
+ writeProfileOwner(userId);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Deleting legacy config file");
}
+ if (!legacy.delete()) {
+ Slog.e(TAG, "Failed to remove the legacy setting file");
+ }
+ } else {
+ // No legacy file, read from the new format files.
+ new DeviceOwnerReadWriter().readFromFileLocked();
+
+ for (UserInfo ui : users) {
+ new ProfileOwnerReadWriter(ui.id).readFromFileLocked();
+ }
+ }
+ mUserManagerInternal.setDeviceManaged(hasDeviceOwner());
+ for (UserInfo ui : users) {
+ mUserManagerInternal.setUserManaged(ui.id, hasProfileOwner(ui.id));
}
if (hasDeviceOwner() && hasProfileOwner(getDeviceOwnerUserId())) {
Slog.w(TAG, String.format("User %d has both DO and PO, which is not supported",
@@ -169,21 +177,27 @@ class Owners {
boolean userRestrictionsMigrated) {
mDeviceOwner = new OwnerInfo(ownerName, admin, userRestrictionsMigrated);
mDeviceOwnerUserId = userId;
+
+ mUserManagerInternal.setDeviceManaged(true);
}
void clearDeviceOwner() {
mDeviceOwner = null;
mDeviceOwnerUserId = UserHandle.USER_NULL;
+
+ mUserManagerInternal.setDeviceManaged(false);
}
void setProfileOwner(ComponentName admin, String ownerName, int userId) {
// For a newly set PO, there's no need for migration.
mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
/* userRestrictionsMigrated =*/ true));
+ mUserManagerInternal.setUserManaged(userId, true);
}
void removeProfileOwner(int userId) {
mProfileOwners.remove(userId);
+ mUserManagerInternal.setUserManaged(userId, false);
}
ComponentName getProfileOwnerComponent(int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 56d6fc05d6b5..435b6026830e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -52,7 +52,7 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
private final File mProfileOwnerBase;
public OwnersTestable(DpmMockContext context) {
- super(context);
+ super(context, context.userManager, context.userManagerInternal);
mLegacyFile = new File(context.dataDir, LEGACY_FILE);
mDeviceOwnerFile = new File(context.dataDir, DEVICE_OWNER_FILE);
mProfileOwnerBase = new File(context.dataDir, PROFILE_OWNER_FILE_BASE);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 32b7383ff9a5..36407e195ac6 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -75,7 +75,7 @@ public class SubscriptionManager {
/**
* Indicates the caller wants the default phone id.
- * Used in SubscriptionController and PhoneBase but do we really need it???
+ * Used in SubscriptionController and Phone but do we really need it???
* @hide
*/
public static final int DEFAULT_PHONE_INDEX = Integer.MAX_VALUE;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 8899e53648d2..01c3c500855d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -184,8 +184,10 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
}
@Override
- public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext,
- EditorInfo attribute, int controlFlags) throws RemoteException {
+ public InputBindResult startInput(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
+ int controlFlags) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -226,9 +228,11 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
}
@Override
- public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
- int controlFlags, int softInputMode, int windowFlags, EditorInfo attribute,
- IInputContext inputContext) throws RemoteException {
+ public InputBindResult windowGainedFocus(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
+ int windowFlags, EditorInfo attribute, IInputContext inputContext)
+ throws RemoteException {
// TODO Auto-generated method stub
return null;
}