fillContexts = request.getFillContexts();
+ return fillContexts.get(fillContexts.size() - 1).getStructure();
+ }
+
+ /**
+ * Parses the {@link AssistStructure} representing the activity being autofilled, and returns a
+ * map of autofillable fields (represented by their autofill ids) mapped by the hint associate
+ * with them.
+ *
+ * An autofillable field is a {@link AssistStructure.ViewNode} whose getHint(ViewNode)
+ * method.
+ */
+ @NonNull
+ private static ArrayMap getAutofillableFields(
+ @NonNull AssistStructure structure) {
+ ArrayMap fields = new ArrayMap<>();
+ int nodes = structure.getWindowNodeCount();
+ for (int i = 0; i < nodes; i++) {
+ AssistStructure.ViewNode node = structure.getWindowNodeAt(i).getRootViewNode();
+ addAutofillableFields(fields, node);
+ }
+ ArrayMap result = new ArrayMap<>();
+ int filedCount = fields.size();
+ for (int i = 0; i < filedCount; i++) {
+ String key = fields.keyAt(i);
+ AutofillId value = fields.valueAt(i);
+ // For fields with no hint we just use Field
+ if (key.equals(value.toString())) {
+ result.put("Field:" + i + "-", fields.valueAt(i));
+ } else {
+ result.put(key, fields.valueAt(i));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Adds any autofillable view from the {@link AssistStructure.ViewNode} and its descendants to
+ * the map.
+ */
+ private static void addAutofillableFields(@NonNull Map fields,
+ @NonNull AssistStructure.ViewNode node) {
+ if (node.getAutofillType() == View.AUTOFILL_TYPE_TEXT) {
+ if (!fields.containsValue(node.getAutofillId())) {
+ final String key;
+ if (node.getHint() != null) {
+ key = node.getHint().toLowerCase();
+ } else if (node.getAutofillHints() != null) {
+ key = node.getAutofillHints()[0].toLowerCase();
+ } else {
+ key = node.getAutofillId().toString();
+ }
+ fields.put(key, node.getAutofillId());
+ }
+ }
+ int childrenSize = node.getChildCount();
+ for (int i = 0; i < childrenSize; i++) {
+ addAutofillableFields(fields, node.getChildAt(i));
+ }
+ }
+}
diff --git a/samples/InlineFillService/src/com/example/android/inlinefillservice/InlineFillService.java b/samples/InlineFillService/src/com/example/android/inlinefillservice/InlineFillService.java
new file mode 100644
index 000000000..6bb9e4fa4
--- /dev/null
+++ b/samples/InlineFillService/src/com/example/android/inlinefillservice/InlineFillService.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 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.example.android.inlinefillservice;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.FillResponse;
+import android.service.autofill.InlinePresentation;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.SaveRequest;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * A basic {@link AutofillService} implementation that only shows dynamic-generated datasets
+ * and supports inline suggestions.
+ */
+public class InlineFillService extends AutofillService {
+
+ static final String TAG = "InlineFillService";
+
+ /**
+ * Number of datasets sent on each request - we're simple, that value is hardcoded in our DNA!
+ */
+ static final int NUMBER_DATASETS = 6;
+
+ private final boolean mAuthenticateResponses = false;
+ private final boolean mAuthenticateDatasets = false;
+
+ @Override
+ public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+ FillCallback callback) {
+ Log.d(TAG, "onFillRequest()");
+
+ final Context context = getApplicationContext();
+
+ // Find autofillable fields
+ ArrayMap fields = Helper.getAutofillableFields(request);
+ Log.d(TAG, "autofillable fields:" + fields);
+ if (fields.isEmpty()) {
+ Helper.showMessage(context,
+ "InlineFillService could not figure out how to autofill this screen");
+ callback.onSuccess(null);
+ return;
+ }
+ final Optional inlineRequest =
+ InlineRequestHelper.getInlineSuggestionsRequest(request);
+ final int maxSuggestionsCount = InlineRequestHelper.getMaxSuggestionCount(inlineRequest,
+ NUMBER_DATASETS);
+
+ // Create the base response
+ final FillResponse response;
+ if (mAuthenticateResponses) {
+ int size = fields.size();
+ String[] hints = new String[size];
+ AutofillId[] ids = new AutofillId[size];
+ for (int i = 0; i < size; i++) {
+ hints[i] = fields.keyAt(i);
+ ids[i] = fields.valueAt(i);
+ }
+ IntentSender authentication = AuthActivity.newIntentSenderForResponse(this, hints,
+ ids, mAuthenticateDatasets, inlineRequest.orElse(null));
+ RemoteViews presentation = ResponseHelper.newDatasetPresentation(getPackageName(),
+ "Tap to auth response");
+
+ InlinePresentation inlinePresentation =
+ InlineRequestHelper.maybeCreateInlineAuthenticationResponse(context,
+ inlineRequest);
+ response = new FillResponse.Builder()
+ .setAuthentication(ids, authentication, presentation, inlinePresentation)
+ .build();
+ } else {
+ response = createResponse(this, fields, maxSuggestionsCount, mAuthenticateDatasets,
+ inlineRequest);
+ }
+
+ callback.onSuccess(response);
+ }
+
+ static FillResponse createResponse(@NonNull Context context,
+ @NonNull ArrayMap fields, int numDatasets,
+ boolean authenticateDatasets,
+ @NonNull Optional inlineRequest) {
+ String packageName = context.getPackageName();
+ FillResponse.Builder response = new FillResponse.Builder();
+ // 1.Add the dynamic datasets
+ for (int i = 0; i < numDatasets; i++) {
+ if (authenticateDatasets) {
+ response.addDataset(ResponseHelper.newLockedDataset(context, fields, packageName, i,
+ inlineRequest));
+ } else {
+ response.addDataset(ResponseHelper.newUnlockedDataset(context, fields,
+ packageName, i, inlineRequest));
+ }
+ }
+
+ // 2. Add some inline actions
+ if (inlineRequest.isPresent()) {
+ response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields,
+ inlineRequest.get(), R.drawable.ic_settings));
+ response.addDataset(InlineRequestHelper.createInlineActionDataset(context, fields,
+ inlineRequest.get(), R.drawable.ic_settings));
+ }
+
+ // 3.Add save info
+ Collection ids = fields.values();
+ AutofillId[] requiredIds = new AutofillId[ids.size()];
+ ids.toArray(requiredIds);
+ response.setSaveInfo(
+ // We're simple, so we're generic
+ new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, requiredIds).build());
+
+ // 4.Profit!
+ return response.build();
+ }
+
+ @Override
+ public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+ Log.d(TAG, "onSaveRequest()");
+ Helper.showMessage(getApplicationContext(), "InlineFillService doesn't support Save");
+ callback.onSuccess();
+ }
+}
diff --git a/samples/InlineFillService/src/com/example/android/inlinefillservice/InlineRequestHelper.java b/samples/InlineFillService/src/com/example/android/inlinefillservice/InlineRequestHelper.java
new file mode 100644
index 000000000..c93771af6
--- /dev/null
+++ b/samples/InlineFillService/src/com/example/android/inlinefillservice/InlineRequestHelper.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 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.example.android.inlinefillservice;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillRequest;
+import android.service.autofill.InlinePresentation;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.widget.inline.InlinePresentationSpec;
+
+import androidx.autofill.inline.v1.InlineSuggestionUi;
+import androidx.autofill.inline.v1.InlineSuggestionUi.Content;
+
+import java.util.Optional;
+
+public class InlineRequestHelper {
+ static Optional getInlineSuggestionsRequest(FillRequest request) {
+ final InlineSuggestionsRequest inlineRequest = request.getInlineSuggestionsRequest();
+ if (inlineRequest != null && inlineRequest.getMaxSuggestionCount() > 0
+ && !inlineRequest.getInlinePresentationSpecs().isEmpty()) {
+ return Optional.of(inlineRequest);
+ }
+ return Optional.empty();
+ }
+
+ static int getMaxSuggestionCount(Optional inlineRequest, int max) {
+ if (inlineRequest.isPresent()) {
+ return Math.min(max, inlineRequest.get().getMaxSuggestionCount());
+ }
+ return max;
+ }
+
+ static InlinePresentation maybeCreateInlineAuthenticationResponse(
+ Context context, Optional inlineRequest) {
+ if (!inlineRequest.isPresent()) {
+ return null;
+ }
+ final PendingIntent attribution = createAttribution(context,
+ "Please tap on the chip to authenticate the Autofill response.");
+ final Slice slice = createSlice("Tap to auth response", null, null, null, attribution);
+ final InlinePresentationSpec spec = inlineRequest.get().getInlinePresentationSpecs().get(0);
+ return new InlinePresentation(slice, spec, false);
+ }
+
+ static InlinePresentation createInlineDataset(Context context,
+ InlineSuggestionsRequest inlineRequest, String value, int index) {
+ final PendingIntent attribution = createAttribution(context,
+ "Please tap on the chip to autofill the value:" + value);
+ final Slice slice = createSlice(value, null, null, null, attribution);
+ index = Math.min(inlineRequest.getInlinePresentationSpecs().size() - 1, index);
+ final InlinePresentationSpec spec = inlineRequest.getInlinePresentationSpecs().get(index);
+ return new InlinePresentation(slice, spec, false);
+ }
+
+ static Dataset createInlineActionDataset(Context context,
+ ArrayMap fields,
+ InlineSuggestionsRequest inlineRequest, int drawable) {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(context, 0, new Intent(context, SettingsActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ Dataset.Builder builder =
+ new Dataset.Builder()
+ .setInlinePresentation(createInlineAction(context, inlineRequest, drawable))
+ .setAuthentication(pendingIntent.getIntentSender());
+ for (AutofillId fieldId : fields.values()) {
+ builder.setValue(fieldId, null);
+ }
+ return builder.build();
+ }
+
+ private static InlinePresentation createInlineAction(Context context,
+ InlineSuggestionsRequest inlineRequest, int drawable) {
+ final PendingIntent attribution = createAttribution(context,
+ "Please tap on the chip to launch the action.");
+ final Icon icon = Icon.createWithResource(context, drawable);
+ final Slice slice = createSlice(null, null, icon, null, attribution);
+ // Reuse the first spec's height for the inline action size, as there isn't dedicated
+ // value from the request for this.
+ final InlinePresentationSpec spec = inlineRequest.getInlinePresentationSpecs().get(0);
+ return new InlinePresentation(slice, spec, true);
+ }
+
+ private static Slice createSlice(
+ String title, String subtitle, Icon startIcon, Icon endIcon,
+ PendingIntent attribution) {
+ Content.Builder builder = InlineSuggestionUi.newContentBuilder(attribution);
+ if (!TextUtils.isEmpty(title)) {
+ builder.setTitle(title);
+ }
+ if (!TextUtils.isEmpty(subtitle)) {
+ builder.setSubtitle(subtitle);
+ }
+ if (startIcon != null) {
+ builder.setStartIcon(startIcon);
+ }
+ if (endIcon != null) {
+ builder.setEndIcon(endIcon);
+ }
+ return builder.build().getSlice();
+ }
+
+ private static PendingIntent createAttribution(Context context, String msg) {
+ Intent intent = new Intent(context, AttributionDialogActivity.class);
+ intent.putExtra(AttributionDialogActivity.KEY_MSG, msg);
+ // Should use different request code to avoid the new intent overriding the old one.
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(
+ context, msg.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return pendingIntent;
+ }
+
+}
diff --git a/samples/InlineFillService/src/com/example/android/inlinefillservice/ResponseHelper.java b/samples/InlineFillService/src/com/example/android/inlinefillservice/ResponseHelper.java
new file mode 100644
index 000000000..59fb0cd75
--- /dev/null
+++ b/samples/InlineFillService/src/com/example/android/inlinefillservice/ResponseHelper.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 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.example.android.inlinefillservice;
+
+import static com.example.android.inlinefillservice.InlineFillService.TAG;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.service.autofill.Dataset;
+import android.service.autofill.InlinePresentation;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+
+import java.util.Map;
+import java.util.Optional;
+
+class ResponseHelper {
+
+ static Dataset newUnlockedDataset(@NonNull Context context,
+ @NonNull Map fields, @NonNull String packageName, int index,
+ @NonNull Optional inlineRequest) {
+
+ Dataset.Builder dataset = new Dataset.Builder();
+ for (Map.Entry field : fields.entrySet()) {
+ final String hint = field.getKey();
+ final AutofillId id = field.getValue();
+ final String value = hint + (index + 1);
+
+ // We're simple - our dataset values are hardcoded as "hintN" (for example,
+ // "username1", "username2") and they're displayed as such, except if they're a
+ // password
+ Log.d(TAG, "hint: " + hint);
+ final String displayValue = hint.contains("password") ? "password for #" + (index + 1)
+ : value;
+ final RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
+
+ // Add Inline Suggestion required info.
+ if (inlineRequest.isPresent()) {
+ Log.d(TAG, "Found InlineSuggestionsRequest in FillRequest: " + inlineRequest);
+ final InlinePresentation inlinePresentation =
+ InlineRequestHelper.createInlineDataset(context, inlineRequest.get(),
+ displayValue, index);
+ dataset.setValue(id, AutofillValue.forText(value), presentation,
+ inlinePresentation);
+ } else {
+ dataset.setValue(id, AutofillValue.forText(value), presentation);
+ }
+ }
+
+ return dataset.build();
+ }
+
+ static Dataset newLockedDataset(@NonNull Context context,
+ @NonNull Map fields, @NonNull String packageName, int index,
+ @NonNull Optional inlineRequest) {
+ Dataset unlockedDataset = ResponseHelper.newUnlockedDataset(context, fields,
+ packageName, index, inlineRequest);
+
+ Dataset.Builder lockedDataset = new Dataset.Builder();
+ for (Map.Entry field : fields.entrySet()) {
+ String hint = field.getKey();
+ AutofillId id = field.getValue();
+ String value = (index + 1) + "-" + hint;
+ String displayValue = "Tap to auth " + value;
+ IntentSender authentication =
+ AuthActivity.newIntentSenderForDataset(context, unlockedDataset);
+ RemoteViews presentation = newDatasetPresentation(packageName, displayValue);
+ if (inlineRequest.isPresent()) {
+ final InlinePresentation inlinePresentation =
+ InlineRequestHelper.createInlineDataset(context, inlineRequest.get(),
+ displayValue, index);
+ lockedDataset.setValue(id, null, presentation, inlinePresentation)
+ .setAuthentication(authentication);
+ } else {
+ lockedDataset.setValue(id, null, presentation)
+ .setAuthentication(authentication);
+ }
+ }
+ return lockedDataset.build();
+ }
+
+ /**
+ * Helper method to create a dataset presentation with the givean text.
+ */
+ @NonNull
+ static RemoteViews newDatasetPresentation(@NonNull String packageName,
+ @NonNull CharSequence text) {
+ RemoteViews presentation =
+ new RemoteViews(packageName, R.layout.list_item);
+ presentation.setTextViewText(R.id.text, text);
+ return presentation;
+ }
+}
diff --git a/tools/winscope/src/stubs/protolog.proto b/samples/InlineFillService/src/com/example/android/inlinefillservice/SettingsActivity.java
similarity index 65%
rename from tools/winscope/src/stubs/protolog.proto
rename to samples/InlineFillService/src/com/example/android/inlinefillservice/SettingsActivity.java
index dba82e3eb..19f6cd1e0 100644
--- a/tools/winscope/src/stubs/protolog.proto
+++ b/samples/InlineFillService/src/com/example/android/inlinefillservice/SettingsActivity.java
@@ -13,9 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-syntax = "proto2";
-package com.android.server.protolog;
+package com.example.android.inlinefillservice;
-message ProtoLogMessage {}
-message ProtoLogFileProto {}
\ No newline at end of file
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SettingsActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.auth_activity);
+ }
+}
diff --git a/samples/IntentPlayground/res/values/strings.xml b/samples/IntentPlayground/res/values/strings.xml
index a6bdcc4cf..4a3683c4a 100644
--- a/samples/IntentPlayground/res/values/strings.xml
+++ b/samples/IntentPlayground/res/values/strings.xml
@@ -92,4 +92,5 @@
Select start mode
Start activity
Start activity for result
+ Start Intent testing failed
diff --git a/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivity.java b/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivity.java
index 02cad285d..227f517b5 100644
--- a/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivity.java
+++ b/samples/IntentPlayground/src/com/example/android/intentplayground/BaseActivity.java
@@ -27,6 +27,8 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
+import android.widget.Toast;
+
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
@@ -238,8 +240,14 @@ public abstract class BaseActivity extends AppCompatActivity implements
}
protected void runIntentTests() {
- startActivity(getPackageManager()
- .getLaunchIntentForPackage("com.example.android.intentplayground.test"));
+ final Intent intent = getPackageManager()
+ .getLaunchIntentForPackage("com.example.android.intentplayground.test");
+ if (intent != null) {
+ startActivity(intent);
+ } else {
+ Toast.makeText(this,
+ R.string.launch_testing_activities_failed, Toast.LENGTH_LONG).show();
+ }
}
protected Intent prepareLaunchForward() {
diff --git a/samples/MultiClientInputMethod/res/values/config.xml b/samples/MultiClientInputMethod/res/values/config.xml
new file mode 100644
index 000000000..133b73e67
--- /dev/null
+++ b/samples/MultiClientInputMethod/res/values/config.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/InputMethodDebug.java b/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/InputMethodDebug.java
index a71bdc892..dd11d212a 100644
--- a/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/InputMethodDebug.java
+++ b/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/InputMethodDebug.java
@@ -110,9 +110,6 @@ final class InputMethodDebug {
if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) {
joiner.add("IS_TEXT_EDITOR");
}
- if ((startInputFlags & StartInputFlags.FIRST_WINDOW_FOCUS_GAIN) != 0) {
- joiner.add("FIRST_WINDOW_FOCUS_GAIN");
- }
if ((startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0) {
joiner.add("INITIAL_CONNECTION");
}
diff --git a/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/MultiClientInputMethod.java b/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/MultiClientInputMethod.java
index 0b146ad35..a3f9d1b74 100644
--- a/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/MultiClientInputMethod.java
+++ b/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/MultiClientInputMethod.java
@@ -16,7 +16,9 @@
package com.example.android.multiclientinputmethod;
+import android.annotation.NonNull;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
@@ -24,6 +26,7 @@ import android.inputmethodservice.MultiClientInputMethodServiceDelegate;
import android.os.IBinder;
import android.util.Log;
import android.util.SparseIntArray;
+import android.view.Display;
/**
* A {@link Service} that implements multi-client IME protocol.
@@ -34,6 +37,9 @@ public final class MultiClientInputMethod extends Service implements DisplayList
// last client that had active InputConnection for a given displayId.
final SparseIntArray mDisplayToLastClientId = new SparseIntArray();
+ // Mapping table from the display where IME is attached to the display where IME window will be
+ // shown. Assumes that missing display will use the same display for the IME window.
+ SparseIntArray mInputDisplayToImeDisplay;
SoftInputWindowManager mSoftInputWindowManager;
MultiClientInputMethodServiceDelegate mDelegate;
@@ -44,6 +50,7 @@ public final class MultiClientInputMethod extends Service implements DisplayList
if (DEBUG) {
Log.v(TAG, "onCreate");
}
+ mInputDisplayToImeDisplay = buildInputDisplayToImeDisplay();
mDelegate = MultiClientInputMethodServiceDelegate.create(this,
new MultiClientInputMethodServiceDelegate.ServiceCallback() {
@Override
@@ -56,13 +63,17 @@ public final class MultiClientInputMethod extends Service implements DisplayList
@Override
public void addClient(int clientId, int uid, int pid,
int selfReportedDisplayId) {
+ int imeDisplayId = mInputDisplayToImeDisplay.get(selfReportedDisplayId,
+ selfReportedDisplayId);
final ClientCallbackImpl callback = new ClientCallbackImpl(
MultiClientInputMethod.this, mDelegate,
- mSoftInputWindowManager, clientId, uid, pid, selfReportedDisplayId);
+ mSoftInputWindowManager, clientId, uid, pid, imeDisplayId);
if (DEBUG) {
Log.v(TAG, "addClient clientId=" + clientId + " uid=" + uid
- + " pid=" + pid + " displayId=" + selfReportedDisplayId);
+ + " pid=" + pid + " displayId=" + selfReportedDisplayId
+ + " imeDisplayId=" + imeDisplayId);
}
+
mDelegate.acceptClient(clientId, callback, callback.getDispatcherState(),
callback.getLooper());
}
@@ -79,6 +90,7 @@ public final class MultiClientInputMethod extends Service implements DisplayList
@Override
public void onDisplayAdded(int displayId) {
+ mInputDisplayToImeDisplay = buildInputDisplayToImeDisplay();
}
@Override
@@ -118,4 +130,41 @@ public final class MultiClientInputMethod extends Service implements DisplayList
}
mDelegate.onDestroy();
}
+
+ @NonNull
+ private SparseIntArray buildInputDisplayToImeDisplay() {
+ Context context = getApplicationContext();
+ String config[] = context.getResources().getStringArray(
+ R.array.config_inputDisplayToImeDisplay);
+
+ SparseIntArray inputDisplayToImeDisplay = new SparseIntArray();
+ Display[] displays = context.getSystemService(DisplayManager.class).getDisplays();
+ for (String item: config) {
+ String[] pair = item.split("/");
+ if (pair.length != 2) {
+ Log.w(TAG, "Skip illegal config: " + item);
+ continue;
+ }
+ int inputDisplay = findDisplayId(displays, pair[0]);
+ int imeDisplay = findDisplayId(displays, pair[1]);
+ if (inputDisplay != Display.INVALID_DISPLAY && imeDisplay != Display.INVALID_DISPLAY) {
+ inputDisplayToImeDisplay.put(inputDisplay, imeDisplay);
+ }
+ }
+ return inputDisplayToImeDisplay;
+ }
+
+ private static int findDisplayId(Display displays[], String regexp) {
+ for (Display display: displays) {
+ if (display.getUniqueId().matches(regexp)) {
+ int displayId = display.getDisplayId();
+ if (DEBUG) {
+ Log.v(TAG, regexp + " matches displayId=" + displayId);
+ }
+ return displayId;
+ }
+ }
+ Log.w(TAG, "Can't find the display of " + regexp);
+ return Display.INVALID_DISPLAY;
+ }
}
diff --git a/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/SoftInputWindow.java b/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/SoftInputWindow.java
index afc66a413..93e60a7a9 100644
--- a/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/SoftInputWindow.java
+++ b/samples/MultiClientInputMethod/src/com/example/android/multiclientinputmethod/SoftInputWindow.java
@@ -78,6 +78,10 @@ final class SoftInputWindow extends Dialog {
mSymbolShiftKeyboard = new Keyboard(context, R.xml.symbols_shift);
mKeyboardView.setKeyboard(mQwertygKeyboard);
mKeyboardView.setOnKeyboardActionListener(sNoopListener);
+
+ // TODO(b/158663354): Disabling keyboard popped preview key since it is currently broken.
+ mKeyboardView.setPreviewEnabled(false);
+
layout.addView(mKeyboardView);
setContentView(layout, new ViewGroup.LayoutParams(
diff --git a/samples/SpellChecker/SampleSpellCheckerService/src/com/example/android/samplespellcheckerservice/SampleSpellCheckerService.java b/samples/SpellChecker/SampleSpellCheckerService/src/com/example/android/samplespellcheckerservice/SampleSpellCheckerService.java
index e0abb4cc0..b867a5cfc 100644
--- a/samples/SpellChecker/SampleSpellCheckerService/src/com/example/android/samplespellcheckerservice/SampleSpellCheckerService.java
+++ b/samples/SpellChecker/SampleSpellCheckerService/src/com/example/android/samplespellcheckerservice/SampleSpellCheckerService.java
@@ -69,7 +69,8 @@ public class SampleSpellCheckerService extends SpellCheckerService {
final int flags = length <= 3 ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
: length <= 20 ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0;
return new SuggestionsInfo(flags,
- new String[] {"aaa", "bbb", "Candidate for " + input, mLocale});
+ new String[] {"aaa", "bbb", "Candidate for " + input, mLocale},
+ textInfo.getCookie(), textInfo.getSequence());
}
/**
@@ -109,11 +110,11 @@ public class SampleSpellCheckerService extends SpellCheckerService {
| SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO;
final int flags2 = flags1;
final SuggestionsInfo si0 = new SuggestionsInfo(
- flags0, new String[] { "would" });
+ flags0, new String[] { "would" }, ti.getCookie(), ti.getSequence());
final SuggestionsInfo si1 = new SuggestionsInfo(
- flags1, new String[] { "hear" });
+ flags1, new String[] { "hear" }, ti.getCookie(), ti.getSequence());
final SuggestionsInfo si2 = new SuggestionsInfo(
- flags2, new String[] { "from" });
+ flags2, new String[] { "from" }, ti.getCookie(), ti.getSequence());
sis = new SuggestionsInfo[] {si0, si1, si2};
offsets = new int[] { 2, 15, 20 };
lengths = new int[] { 4, 4, 4 };
@@ -125,13 +126,14 @@ public class SampleSpellCheckerService extends SpellCheckerService {
final int flags = length <= 3 ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
: length <= 20 ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0;
final SuggestionsInfo si = new SuggestionsInfo(flags,
- new String[] {"aaa", "bbb", "Candidate for " + input, mLocale});
+ new String[] {"aaa", "bbb", "Candidate for " + input, mLocale},
+ ti.getCookie(), ti.getSequence());
sis = new SuggestionsInfo[] { si };
offsets = new int[] { 0 };
lengths = new int[] { ti.getText().length() };
}
final SentenceSuggestionsInfo ssi =
- new SentenceSuggestionsInfo(sis, lengths, offsets);
+ new SentenceSuggestionsInfo(sis, offsets, lengths);
retval.add(ssi);
}
return retval.toArray(new SentenceSuggestionsInfo[0]);
diff --git a/scripts/codegen b/scripts/codegen
new file mode 100755
index 000000000..d53dd3774
--- /dev/null
+++ b/scripts/codegen
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+top="$(dirname $0)/../.."
+
+function buildCodegen() {
+ $top/build/soong/soong_ui.bash --build-mode --all-modules --dir="$PWD" -j codegen_cli 1>&2
+}
+
+if ! command -v codegen_cli 2>&1 >/dev/null; then
+ # First time running codegen
+ buildCodegen
+else
+ latestVersion=$(cat $top/frameworks/base/tools/codegen/src/com/android/codegen/SharedConstants.kt | grep 'CODEGEN_VERSION =' | awk '{ print $5 }' | tr -d '"')
+ if [[ $(codegen_cli --version) != $latestVersion ]]; then
+ # Update codegen
+ buildCodegen
+ fi
+fi
+
+exec codegen_cli "$@"
diff --git a/sdk/build_tools_source.prop_template b/sdk/build_tools_source.prop_template
index fd289fd01..49c19adf1 100644
--- a/sdk/build_tools_source.prop_template
+++ b/sdk/build_tools_source.prop_template
@@ -1,3 +1,3 @@
Pkg.UserSrc=false
-Pkg.Revision=${PLATFORM_SDK_VERSION}.0.3
-#Pkg.Revision=29.0.0 rc4
+Pkg.Revision=${PLATFORM_SDK_VERSION}.0.1
+#Pkg.Revision=30.0.0 rc4
diff --git a/sdk/platform_source.prop_template b/sdk/platform_source.prop_template
index ff513ff0c..0093ab133 100644
--- a/sdk/platform_source.prop_template
+++ b/sdk/platform_source.prop_template
@@ -2,7 +2,7 @@ Pkg.Desc=Android SDK Platform ${PLATFORM_VERSION}
Pkg.UserSrc=false
Platform.Version=${PLATFORM_VERSION}
Platform.CodeName=
-Pkg.Revision=4
+Pkg.Revision=2
AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
Layoutlib.Api=15
diff --git a/sys-img/advancedFeatures.ini b/sys-img/advancedFeatures.ini
index e1f8b6ade..f8410eb33 100644
--- a/sys-img/advancedFeatures.ini
+++ b/sys-img/advancedFeatures.ini
@@ -8,3 +8,5 @@ IntelPerformanceMonitoringUnit = on
Wifi = on
HostComposition = on
DynamicPartition = on
+GLDirectMem = on
+Vulkan = on
diff --git a/sys-img/images_arm64-v8a_source.prop_template b/sys-img/images_arm64-v8a_source.prop_template
index 5a0bcc4c8..54f52d56e 100644
--- a/sys-img/images_arm64-v8a_source.prop_template
+++ b/sys-img/images_arm64-v8a_source.prop_template
@@ -1,6 +1,6 @@
Pkg.Desc=Android SDK System Image
Pkg.UserSrc=false
-Pkg.Revision=6
+Pkg.Revision=1
AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
SystemImage.Abi=arm64-v8a
diff --git a/sys-img/images_armeabi-v7a_source.prop_template b/sys-img/images_armeabi-v7a_source.prop_template
index 7e05c2740..eb6837457 100644
--- a/sys-img/images_armeabi-v7a_source.prop_template
+++ b/sys-img/images_armeabi-v7a_source.prop_template
@@ -1,6 +1,6 @@
Pkg.Desc=Android SDK System Image
Pkg.UserSrc=false
-Pkg.Revision=6
+Pkg.Revision=1
Pkg.Dependencies=emulator#28.1.9
AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
diff --git a/sys-img/images_armeabi_source.prop_template b/sys-img/images_armeabi_source.prop_template
index 58039b27b..cd926d095 100644
--- a/sys-img/images_armeabi_source.prop_template
+++ b/sys-img/images_armeabi_source.prop_template
@@ -1,6 +1,6 @@
Pkg.Desc=Android SDK System Image
Pkg.UserSrc=false
-Pkg.Revision=6
+Pkg.Revision=1
Pkg.Dependencies=emulator#28.1.9
AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
diff --git a/sys-img/images_x86_64_source.prop_template b/sys-img/images_x86_64_source.prop_template
index 5b28d9d9c..26fdbdc9a 100644
--- a/sys-img/images_x86_64_source.prop_template
+++ b/sys-img/images_x86_64_source.prop_template
@@ -1,7 +1,7 @@
Pkg.Desc=Android SDK System Image
Pkg.UserSrc=false
-Pkg.Revision=6
-Pkg.Dependencies=emulator#28.1.9
+Pkg.Revision=7
+Pkg.Dependencies=emulator#29.1.11
AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
SystemImage.Abi=x86_64
diff --git a/sys-img/images_x86_source.prop_template b/sys-img/images_x86_source.prop_template
index 59006fe62..438422b16 100644
--- a/sys-img/images_x86_source.prop_template
+++ b/sys-img/images_x86_source.prop_template
@@ -1,7 +1,7 @@
Pkg.Desc=Android SDK System Image
Pkg.UserSrc=false
-Pkg.Revision=6
-Pkg.Dependencies=emulator#28.1.9
+Pkg.Revision=7
+Pkg.Dependencies=emulator#29.1.11
AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
SystemImage.Abi=x86
diff --git a/tools/winscope/README.md b/tools/winscope/README.md
index ca6c12b5d..8821601f0 100644
--- a/tools/winscope/README.md
+++ b/tools/winscope/README.md
@@ -17,3 +17,6 @@ contain the proto definitions for their internal states.
* Navigate to `development/tools/winscope`
* Run `yarn run dev`
+### Building with internal extensions
+Internal paths in vendor/ which are not available in AOSP must be replaced by
+stub files. See getWaylandSafePath for an example
diff --git a/tools/winscope/package.json b/tools/winscope/package.json
index da34b904c..617f577db 100644
--- a/tools/winscope/package.json
+++ b/tools/winscope/package.json
@@ -5,8 +5,8 @@
"author": "Adrian Roos ",
"private": true,
"scripts": {
- "dev": "cross-env AOSP=true NODE_ENV=development webpack-dev-server --open --hot",
- "build": "cross-env AOSP=true NODE_ENV=production webpack --progress --hide-modules"
+ "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
+ "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
},
"dependencies": {
"vue": "^2.3.3",
diff --git a/tools/winscope/src/DataView.vue b/tools/winscope/src/DataView.vue
index 5fce2dfd1..40d1b4891 100644
--- a/tools/winscope/src/DataView.vue
+++ b/tools/winscope/src/DataView.vue
@@ -56,7 +56,10 @@ export default {
isTrace() {
return this.file.type == DATA_TYPES.WINDOW_MANAGER ||
this.file.type == DATA_TYPES.SURFACE_FLINGER ||
- this.file.type == DATA_TYPES.TRANSACTION || this.file.type == DATA_TYPES.WAYLAND
+ this.file.type == DATA_TYPES.TRANSACTION ||
+ this.file.type == DATA_TYPES.WAYLAND ||
+ this.file.type == DATA_TYPES.SYSTEM_UI ||
+ this.file.type == DATA_TYPES.LAUNCHER
},
isVideo() {
return this.file.type == DATA_TYPES.SCREEN_RECORDING;
diff --git a/tools/winscope/src/Rects.vue b/tools/winscope/src/Rects.vue
index 49d4da979..9548d7e3e 100644
--- a/tools/winscope/src/Rects.vue
+++ b/tools/winscope/src/Rects.vue
@@ -24,6 +24,8 @@
diff --git a/tools/winscope/src/TreeView.vue b/tools/winscope/src/TreeView.vue
index c7050f0db..9445c522e 100644
--- a/tools/winscope/src/TreeView.vue
+++ b/tools/winscope/src/TreeView.vue
@@ -30,10 +30,6 @@ import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanag
import protobuf from 'protobufjs'
var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs);
-var TraceMessage = protoDefs.lookupType(
- "com.android.server.wm.WindowManagerTraceFileProto");
-var ServiceMessage = protoDefs.lookupType(
- "com.android.server.wm.WindowManagerServiceDumpProto");
export default {
name: 'tree-view',
@@ -84,14 +80,19 @@ export default {
];
},
filterMatches(c) {
+ // If a filter is set, consider the item matches if the current item or any of its
+ // children matches.
if (this.filter) {
- return this.filter(c, this.applyingFlattened);
+ var thisMatches = this.filter(c);
+ const childMatches = (child) => this.filterMatches(child);
+ return thisMatches || (!this.applyingFlattened &&
+ c.children && c.children.some(childMatches));
}
return true;
},
childFilter(c) {
- if (this.filter && this.filter.includeChildren) {
- if (this.filterMatches(c)) {
+ if (this.filter) {
+ if (this.filter(c)) {
// Filter matched c, don't apply further filtering on c's children.
return undefined;
}
@@ -155,4 +156,14 @@ export default {
color: black;
}
+.tree-view-chip.tree-view-chip-gpu {
+ background-color: #00c853;
+ color: black;
+}
+
+.tree-view-chip.tree-view-chip-hwc {
+ background-color: #448aff;
+ color: black;
+}
+
diff --git a/tools/winscope/src/decode.js b/tools/winscope/src/decode.js
index 9285d77f4..94a3de07a 100644
--- a/tools/winscope/src/decode.js
+++ b/tools/winscope/src/decode.js
@@ -15,45 +15,42 @@
*/
-import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
-import jsonProtoLogDefs from 'ProtoLogSafePath/protolog.proto'
-import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
-import jsonProtoDefsTrans from 'frameworks/native/cmds/surfacereplayer/proto/src/trace.proto'
-import jsonProtoDefsWL from 'WaylandSafePath/waylandtrace.proto'
+import jsonProtoDefsWm from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
+import jsonProtoDefsProtoLog from 'frameworks/base/core/proto/android/server/protolog.proto'
+import jsonProtoDefsSf from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
+import jsonProtoDefsTransaction from 'frameworks/native/cmds/surfacereplayer/proto/src/trace.proto'
+import jsonProtoDefsWl from 'WaylandSafePath/waylandtrace.proto'
+import jsonProtoDefsSysUi from 'frameworks/base/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto'
+import jsonProtoDefsLauncher from 'packages/apps/Launcher3/protos/launcher_trace_file.proto'
import protobuf from 'protobufjs'
import { transform_layers, transform_layers_trace } from './transform_sf.js'
import { transform_window_service, transform_window_trace } from './transform_wm.js'
import { transform_transaction_trace } from './transform_transaction.js'
import { transform_wl_outputstate, transform_wayland_trace } from './transform_wl.js'
import { transform_protolog } from './transform_protolog.js'
+import { transform_sysui_trace } from './transform_sys_ui.js'
+import { transform_launcher_trace } from './transform_launcher.js'
import { fill_transform_data } from './matrix_utils.js'
import { mp4Decoder } from './decodeVideo.js'
-var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
- .addJSON(jsonProtoLogDefs.nested)
- .addJSON(jsonProtoDefsSF.nested)
- .addJSON(jsonProtoDefsTrans.nested)
- .addJSON(jsonProtoDefsWL.nested);
-
-var WindowTraceMessage = protoDefs.lookupType(
- "com.android.server.wm.WindowManagerTraceFileProto");
-var WindowMessage = protoDefs.lookupType(
- "com.android.server.wm.WindowManagerServiceDumpProto");
-var LayersMessage = protoDefs.lookupType("android.surfaceflinger.LayersProto");
-var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTraceFileProto");
-var TransactionMessage = protoDefs.lookupType("Trace");
-var WaylandMessage = protoDefs.lookupType("org.chromium.arc.wayland_composer.OutputStateProto");
-var WaylandTraceMessage = protoDefs.lookupType("org.chromium.arc.wayland_composer.TraceFileProto");
-var WindowLogMessage = protoDefs.lookupType(
- "com.android.server.protolog.ProtoLogFileProto");
-var LogMessage = protoDefs.lookupType(
- "com.android.server.protolog.ProtoLogMessage");
+var WmTraceMessage = lookup_type(jsonProtoDefsWm, "com.android.server.wm.WindowManagerTraceFileProto");
+var WmDumpMessage = lookup_type(jsonProtoDefsWm, "com.android.server.wm.WindowManagerServiceDumpProto");
+var SfTraceMessage = lookup_type(jsonProtoDefsSf, "android.surfaceflinger.LayersTraceFileProto");
+var SfDumpMessage = lookup_type(jsonProtoDefsSf, "android.surfaceflinger.LayersProto");
+var SfTransactionTraceMessage = lookup_type(jsonProtoDefsTransaction, "Trace");
+var WaylandTraceMessage = lookup_type(jsonProtoDefsWl, "org.chromium.arc.wayland_composer.TraceFileProto");
+var WaylandDumpMessage = lookup_type(jsonProtoDefsWl, "org.chromium.arc.wayland_composer.OutputStateProto");
+var ProtoLogMessage = lookup_type(jsonProtoDefsProtoLog, "com.android.server.protolog.ProtoLogFileProto");
+var SystemUiTraceMessage = lookup_type(jsonProtoDefsSysUi, "com.android.systemui.tracing.SystemUiTraceFileProto");
+var LauncherTraceMessage = lookup_type(jsonProtoDefsLauncher, "com.android.launcher3.tracing.LauncherTraceFileProto");
const LAYER_TRACE_MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45] // .LYRTRACE
const WINDOW_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45] // .WINTRACE
const MPEG4_MAGIC_NMBER = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32] // ....ftypmp42
const WAYLAND_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x59, 0x4c, 0x54, 0x52, 0x41, 0x43, 0x45] // .WYLTRACE
const PROTO_LOG_MAGIC_NUMBER = [0x09, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47] // .PROTOLOG
+const SYSTEM_UI_MAGIC_NUMBER = [0x09, 0x53, 0x59, 0x53, 0x55, 0x49, 0x54, 0x52, 0x43] // .SYSUITRC
+const LAUNCHER_MAGIC_NUMBER = [0x09, 0x4C, 0x4E, 0x43, 0x48, 0x52, 0x54, 0x52, 0x43] // .LNCHRTRC
const DATA_TYPES = {
WINDOW_MANAGER: {
@@ -85,7 +82,17 @@ const DATA_TYPES = {
name: "ProtoLog",
icon: "notes",
mime: "application/octet-stream",
- }
+ },
+ SYSTEM_UI: {
+ name: "SystemUI",
+ icon: "filter_none",
+ mime: "application/octet-stream",
+ },
+ LAUNCHER: {
+ name: "Launcher",
+ icon: "filter_none",
+ mime: "application/octet-stream",
+ },
}
const FILE_TYPES = {
@@ -94,7 +101,7 @@ const FILE_TYPES = {
dataType: DATA_TYPES.WINDOW_MANAGER,
decoder: protoDecoder,
decoderParams: {
- protoType: WindowTraceMessage,
+ protoType: WmTraceMessage,
transform: transform_window_trace,
timeline: true,
},
@@ -104,7 +111,7 @@ const FILE_TYPES = {
dataType: DATA_TYPES.SURFACE_FLINGER,
decoder: protoDecoder,
decoderParams: {
- protoType: LayersTraceMessage,
+ protoType: SfTraceMessage,
transform: transform_layers_trace,
timeline: true,
},
@@ -124,8 +131,8 @@ const FILE_TYPES = {
dataType: DATA_TYPES.SURFACE_FLINGER,
decoder: protoDecoder,
decoderParams: {
- protoType: LayersMessage,
- transform: transform_layers,
+ protoType: SfDumpMessage,
+ transform: (decoded) => transform_layers(true /*includesCompositionState*/, decoded),
timeline: false,
},
},
@@ -134,7 +141,7 @@ const FILE_TYPES = {
dataType: DATA_TYPES.WINDOW_MANAGER,
decoder: protoDecoder,
decoderParams: {
- protoType: WindowMessage,
+ protoType: WmDumpMessage,
transform: transform_window_service,
timeline: false,
},
@@ -144,7 +151,7 @@ const FILE_TYPES = {
dataType: DATA_TYPES.WAYLAND,
decoder: protoDecoder,
decoderParams: {
- protoType: WaylandMessage,
+ protoType: WaylandDumpMessage,
transform: transform_wl_outputstate,
timeline: false,
},
@@ -162,7 +169,7 @@ const FILE_TYPES = {
dataType: DATA_TYPES.TRANSACTION,
decoder: protoDecoder,
decoderParams: {
- protoType: TransactionMessage,
+ protoType: SfTransactionTraceMessage,
transform: transform_transaction_trace,
timeline: true,
}
@@ -172,13 +179,37 @@ const FILE_TYPES = {
dataType: DATA_TYPES.PROTO_LOG,
decoder: protoDecoder,
decoderParams: {
- protoType: WindowLogMessage,
+ protoType: ProtoLogMessage,
transform: transform_protolog,
timeline: true,
}
- }
+ },
+ 'system_ui_trace': {
+ name: "SystemUI trace",
+ dataType: DATA_TYPES.SYSTEM_UI,
+ decoder: protoDecoder,
+ decoderParams: {
+ protoType: SystemUiTraceMessage,
+ transform: transform_sysui_trace,
+ timeline: true,
+ }
+ },
+ 'launcher_trace': {
+ name: "Launcher trace",
+ dataType: DATA_TYPES.LAUNCHER,
+ decoder: protoDecoder,
+ decoderParams: {
+ protoType: LauncherTraceMessage,
+ transform: transform_launcher_trace,
+ timeline: true,
+ }
+ },
};
+function lookup_type(protoPath, type) {
+ return protobuf.Root.fromJSON(protoPath).lookupType(type);
+}
+
// Replace enum values with string representation and
// add default values to the proto objects. This function also handles
// a special case with TransformProtos where the matrix may be derived
@@ -285,6 +316,12 @@ function detectAndDecode(buffer, fileName, store) {
if (arrayStartsWith(buffer, PROTO_LOG_MAGIC_NUMBER)) {
return decodedFile(FILE_TYPES['proto_log'], buffer, fileName, store);
}
+ if (arrayStartsWith(buffer, SYSTEM_UI_MAGIC_NUMBER)) {
+ return decodedFile(FILE_TYPES['system_ui_trace'], buffer, fileName, store);
+ }
+ if (arrayStartsWith(buffer, LAUNCHER_MAGIC_NUMBER)) {
+ return decodedFile(FILE_TYPES['launcher_trace'], buffer, fileName, store);
+ }
for (var name of ['transaction', 'layers_dump', 'window_dump', 'wl_dump']) {
try {
return decodedFile(FILE_TYPES[name], buffer, fileName, store);
diff --git a/tools/winscope/src/matrix_utils.js b/tools/winscope/src/matrix_utils.js
index 6d7d44fdb..cfada074e 100644
--- a/tools/winscope/src/matrix_utils.js
+++ b/tools/winscope/src/matrix_utils.js
@@ -172,4 +172,40 @@ function is_type_flag_clear(transform, bits) {
return (type & bits) === 0;
}
-export {format_transform_type, fill_transform_data, is_simple_transform};
\ No newline at end of file
+function multiply_vec2(matrix, x, y) {
+ if (!matrix) return {x, y};
+ // |dsdx dsdy tx| | x |
+ // |dtdx dtdy ty| x | y |
+ // |0 0 1 | | 1 |
+ return {
+ x: matrix.dsdx * x + matrix.dsdy * y + matrix.tx,
+ y: matrix.dtdx * x + matrix.dtdy * y + matrix.ty
+ };
+}
+
+function multiply_rect(matrix, rect) {
+ // |dsdx dsdy tx| | left, top |
+ // matrix = |dtdx dtdy ty| rect = | |
+ // |0 0 1 | | right, bottom |
+
+ var left_top = multiply_vec2(matrix, rect.left, rect.top);
+ var right_top = multiply_vec2(matrix, rect.right, rect.top);
+ var left_bottom = multiply_vec2(matrix, rect.left, rect.bottom);
+ var right_bottom = multiply_vec2(matrix, rect.right, rect.bottom);
+
+ var outrect = {};
+ outrect.left = Math.min(left_top.x, right_top.x, left_bottom.x, right_bottom.x);
+ outrect.top = Math.min(left_top.y, right_top.y, left_bottom.y, right_bottom.y);
+ outrect.right = Math.max(left_top.x, right_top.x, left_bottom.x, right_bottom.x);
+ outrect.bottom = Math.max(left_top.y, right_top.y, left_bottom.y, right_bottom.y);
+ return outrect;
+}
+
+// Returns true if the applying the transform on an an axis aligned rectangle
+// results in another axis aligned rectangle.
+function is_simple_rotation(transform) {
+ return !is_type_flag_set(transform, ROT_INVALID_VAL);
+}
+
+export {format_transform_type, fill_transform_data, is_simple_transform,
+ multiply_rect, is_simple_rotation};
\ No newline at end of file
diff --git a/tools/winscope/src/sf_visibility.js b/tools/winscope/src/sf_visibility.js
new file mode 100644
index 000000000..3c70e95ee
--- /dev/null
+++ b/tools/winscope/src/sf_visibility.js
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2020, 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.
+ */
+
+/**
+ * Utility class for deriving state and visibility from the hierarchy. This
+ * duplicates some of the logic in surface flinger. If the trace contains
+ * composition state (visibleRegion), it will be used otherwise it will be
+ * derived.
+ */
+import { multiply_rect, is_simple_rotation } from './matrix_utils.js'
+
+// Layer flags
+const FLAG_HIDDEN = 0x01;
+const FLAG_OPAQUE = 0x02;
+const FLAG_SECURE = 0x80;
+
+function flags_to_string(flags) {
+ if (!flags) return '';
+ var verboseFlags = [];
+ if (flags & FLAG_HIDDEN) verboseFlags.push("HIDDEN");
+ if (flags & FLAG_OPAQUE) verboseFlags.push("OPAQUE");
+ if (flags & FLAG_SECURE) verboseFlags.push("SECURE");
+ return verboseFlags.join('|') + " (" + flags + ")";
+}
+
+function is_empty(region) {
+ return region == undefined ||
+ region.rect == undefined ||
+ region.rect.length == 0 ||
+ region.rect.every(function(r) { return is_empty_rect(r) } );
+}
+
+function is_empty_rect(rect) {
+ var right = rect.right || 0;
+ var left = rect.left || 0;
+ var top = rect.top || 0;
+ var bottom = rect.bottom || 0;
+
+ return (right - left) <= 0 || (bottom - top) <= 0;
+}
+
+function is_rect_empty_and_valid(rect) {
+ return rect &&
+ (rect.left - rect.right === 0 || rect.top - rect.bottom === 0);
+}
+
+/**
+ * The transformation matrix is defined as the product of:
+ * | cos(a) -sin(a) | \/ | X 0 |
+ * | sin(a) cos(a) | /\ | 0 Y |
+ *
+ * where a is a rotation angle, and X and Y are scaling factors.
+ * A transformation matrix is invalid when either X or Y is zero,
+ * as a rotation matrix is valid for any angle. When either X or Y
+ * is 0, then the scaling matrix is not invertible, which makes the
+ * transformation matrix not invertible as well. A 2D matrix with
+ * components | A B | is not invertible if and only if AD - BC = 0.
+ * | C D |
+ * This check is included above.
+ */
+function is_transform_invalid(transform) {
+ return !transform || (transform.dsdx * transform.dtdy ===
+ transform.dtdx * transform.dsdy); //determinant of transform
+}
+
+function is_opaque(layer) {
+ if (layer.color == undefined || layer.color.a == undefined || layer.color.a != 1) return false;
+ return layer.isOpaque;
+}
+
+function fills_color(layer) {
+ return layer.color && layer.color.a > 0 &&
+ layer.color.r >= 0 && layer.color.g >= 0 &&
+ layer.color.b >= 0;
+}
+
+function draws_shadows(layer) {
+ return layer.shadowRadius && layer.shadowRadius > 0;
+}
+
+function has_blur(layer) {
+ return layer.backgroundBlurRadius && layer.backgroundBlurRadius > 0;
+}
+
+function has_effects(layer) {
+ // Support previous color layer
+ if (layer.type === 'ColorLayer') return true;
+
+ // Support newer effect layer
+ return layer.type === 'EffectLayer' &&
+ (fills_color(layer) || draws_shadows(layer) || has_blur(layer))
+}
+
+function is_hidden_by_policy(layer) {
+ return layer.flags & FLAG_HIDDEN == FLAG_HIDDEN ||
+ // offscreen layer root has a unique layer id
+ layer.id == 0x7FFFFFFD;
+}
+
+/**
+ * Checks if the layer is visible based on its visibleRegion if available
+ * or its type, active buffer content, alpha and properties.
+ */
+function is_visible(layer, hiddenByPolicy, includesCompositionState) {
+
+ if (includesCompositionState) {
+ return !is_empty(layer.visibleRegion);
+ }
+
+ if (hiddenByPolicy) {
+ return false;
+ }
+
+ if (!layer.activeBuffer && !has_effects(layer)) {
+ return false;
+ }
+
+ if (!layer.color || !layer.color.a || layer.color.a == 0) {
+ return false;
+ }
+
+ if (layer.occludedBy && layer.occludedBy.length > 0) {
+ return false;
+ }
+
+ if (!layer.bounds || is_empty_rect(layer.bounds)) {
+ return false;
+ }
+
+ return true;
+}
+
+function get_visibility_reason(layer) {
+ if (layer.type === 'ContainerLayer') {
+ return 'ContainerLayer';
+ }
+
+ if (is_hidden_by_policy(layer)) {
+ return 'Flag is hidden';
+ }
+
+ if (layer.hidden) {
+ return 'Hidden by parent';
+ }
+
+ let isBufferLayer = (layer.type === 'BufferStateLayer' || layer.type === 'BufferQueueLayer');
+ if (isBufferLayer && (!layer.activeBuffer ||
+ layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) {
+ return 'Buffer is empty';
+ }
+
+ if (!layer.color || !layer.color.a || layer.color.a == 0) {
+ return 'Alpha is 0';
+ }
+
+ if (is_rect_empty_and_valid(layer.crop)) {
+ return 'Crop is 0x0';
+ }
+
+ if (!layer.bounds || is_empty_rect(layer.bounds)) {
+ return 'Bounds is 0x0';
+ }
+
+ if (is_transform_invalid(layer.transform)) {
+ return 'Transform is invalid';
+ }
+ if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) {
+ return 'RelativeOf layer has been removed';
+ }
+
+ let isEffectLayer = (layer.type === 'EffectLayer');
+ if (isEffectLayer && !fills_color(layer) && !draws_shadows(layer) && !has_blur(layer)) {
+ return 'Effect layer does not have color fill, shadow or blur';
+ }
+
+ if (layer.occludedBy && layer.occludedBy.length > 0) {
+ return 'Layer is occluded by:' + layer.occludedBy.join();
+ }
+
+ if (layer.visible) {
+ return "Unknown";
+ };
+}
+
+// Returns true if rectA overlaps rectB
+function overlaps(rectA, rectB) {
+ return rectA.left < rectB.right && rectA.right > rectB.left &&
+ rectA.top < rectB.bottom && rectA.bottom > rectA.top;
+}
+
+// Returns true if outer rect contains inner rect
+function contains(outerLayer, innerLayer) {
+ if (!is_simple_rotation(outerLayer.transform) || !is_simple_rotation(innerLayer.transform)) {
+ return false;
+ }
+ const outer = screen_bounds(outerLayer);
+ const inner = screen_bounds(innerLayer);
+ return inner.left >= outer.left && inner.top >= outer.top &&
+ inner.right <= outer.right && inner.bottom <= outer.bottom;
+}
+
+function screen_bounds(layer) {
+ if (layer.screenBounds) return layer.screenBounds;
+ let transformMatrix = layer.transform;
+ var tx = layer.position ? layer.position.x || 0 : 0;
+ var ty = layer.position ? layer.position.y || 0 : 0;
+
+ transformMatrix.tx = tx
+ transformMatrix.ty = ty
+ return multiply_rect(transformMatrix, layer.bounds);
+}
+
+// Traverse in z-order from top to bottom and fill in occlusion data
+function fill_occlusion_state(layerMap, rootLayers, includesCompositionState) {
+ const layers = rootLayers.filter(layer => !layer.isRelativeOf);
+ traverse_top_to_bottom(layerMap, layers, {opaqueRects:[], transparentRects:[], screenBounds:null}, (layer, globalState) => {
+
+ if (layer.name.startsWith("Root#0") && layer.sourceBounds) {
+ globalState.screenBounds = {left:0, top:0, bottom:layer.sourceBounds.bottom, right:layer.sourceBounds.right};
+ }
+
+ const visible = is_visible(layer, layer.hidden, includesCompositionState);
+ if (visible) {
+ let fullyOccludes = (testLayer) => contains(testLayer, layer);
+ let partiallyOccludes = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer));
+ let covers = (testLayer) => overlaps(screen_bounds(testLayer), screen_bounds(layer));
+
+ layer.occludedBy = globalState.opaqueRects.filter(fullyOccludes).map(layer => layer.id);
+ layer.partiallyOccludedBy = globalState.opaqueRects.filter(partiallyOccludes).map(layer => layer.id);
+ layer.coveredBy = globalState.transparentRects.filter(covers).map(layer => layer.id);
+
+ if (is_opaque(layer)) {
+ globalState.opaqueRects.push(layer);
+ } else {
+ globalState.transparentRects.push(layer);
+ }
+ }
+
+ layer.visible = is_visible(layer, layer.hidden, includesCompositionState);
+ if (!layer.visible) {
+ layer.invisibleDueTo = get_visibility_reason(layer);
+ }
+ });
+}
+
+function traverse_top_to_bottom(layerMap, rootLayers, globalState, fn) {
+ for (var i = rootLayers.length-1; i >=0; i--) {
+ const relatives = rootLayers[i].relatives.map(id => layerMap[id]);
+ const children = rootLayers[i].children.map(id => layerMap[id])
+
+ // traverse through relatives and children that are not relatives
+ const traverseList = relatives.concat(children.filter(layer => !layer.isRelativeOf));
+ traverseList.sort((lhs, rhs) => rhs.z - lhs.z);
+
+ traverseList.filter((layer) => layer.z >=0).forEach(layer => {
+ traverse_top_to_bottom(layerMap, [layer], globalState, fn);
+ });
+
+ fn(rootLayers[i], globalState);
+
+ traverseList.filter((layer) => layer.z < 0).forEach(layer => {
+ traverse_top_to_bottom(layerMap, [layer], globalState, fn);
+ });
+
+ }
+}
+
+// Traverse all children and fill in any inherited states.
+function fill_inherited_state(layerMap, rootLayers) {
+ traverse(layerMap, rootLayers, (layer, parent) => {
+ const parentHidden = parent && parent.hidden;
+ layer.hidden = is_hidden_by_policy(layer) || parentHidden;
+ layer.verboseFlags = flags_to_string(layer.flags);
+
+ if (!layer.bounds) {
+ if (!layer.sourceBounds) {
+ layer.bounds = layer.sourceBounds;
+ } else if (parent) {
+ layer.bounds = parent.bounds;
+ } else {
+ layer.bounds = {left:0, top:0, right:0, bottom:0};
+ }
+ }
+ });
+}
+
+function traverse(layerMap, rootLayers, fn) {
+ for (var i = rootLayers.length-1; i >=0; i--) {
+ const parentId = rootLayers[i].parent;
+ const parent = parentId == -1 ? null : layerMap[parentId];
+ fn(rootLayers[i], parent);
+ const children = rootLayers[i].children.map(id => layerMap[id]);
+ traverse(layerMap, children, fn);
+ }
+}
+
+export {fill_occlusion_state, fill_inherited_state};
\ No newline at end of file
diff --git a/tools/winscope/src/stubs/services.core.protolog.json b/tools/winscope/src/stubs/services.core.protolog.json
deleted file mode 100644
index 9eecd8f5b..000000000
--- a/tools/winscope/src/stubs/services.core.protolog.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "version": "1.0.0"
-}
\ No newline at end of file
diff --git a/tools/winscope/src/transform_launcher.js b/tools/winscope/src/transform_launcher.js
new file mode 100644
index 000000000..98b49a825
--- /dev/null
+++ b/tools/winscope/src/transform_launcher.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019, 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.
+ */
+
+import {transform, nanos_to_string, get_visible_chip} from './transform.js'
+
+function transform_launcher(launcher) {
+ return transform({
+ obj: launcher,
+ kind: 'launcher',
+ name: 'launcher',
+ children: []
+ });
+}
+
+function transform_entry(entry) {
+ return transform({
+ obj: entry,
+ kind: 'entry',
+ name: nanos_to_string(entry.elapsedRealtimeNanos),
+ children: [
+ [[entry.launcher], transform_launcher]
+ ],
+ timestamp: entry.elapsedRealtimeNanos,
+ stableId: 'entry'
+ });
+}
+
+function transform_launcher_trace(entries) {
+ return transform({
+ obj: entries,
+ kind: 'entries',
+ name: 'entries',
+ children: [
+ [entries.entry, transform_entry],
+ ],
+ });
+}
+
+export {transform_launcher_trace};
diff --git a/tools/winscope/src/transform_protolog.js b/tools/winscope/src/transform_protolog.js
index 78e7c3207..89e87d557 100644
--- a/tools/winscope/src/transform_protolog.js
+++ b/tools/winscope/src/transform_protolog.js
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import viewerConfig from "ProtoLogJsonSafePath/services.core.protolog.json"
+import viewerConfig from "../../../../frameworks/base/data/etc/services.core.protolog.json"
import { nanos_to_string } from './transform.js'
diff --git a/tools/winscope/src/transform_sf.js b/tools/winscope/src/transform_sf.js
index 446af29a8..7b631c68f 100644
--- a/tools/winscope/src/transform_sf.js
+++ b/tools/winscope/src/transform_sf.js
@@ -15,11 +15,7 @@
*/
import {transform, nanos_to_string, get_visible_chip} from './transform.js'
-
-// Layer flags
-const FLAG_HIDDEN = 0x01;
-const FLAG_OPAQUE = 0x02;
-const FLAG_SECURE = 0x80;
+import { fill_occlusion_state, fill_inherited_state } from './sf_visibility.js';
var RELATIVE_Z_CHIP = {short: 'RelZ',
long: "Is relative Z-ordered to another surface",
@@ -30,61 +26,14 @@ var RELATIVE_Z_PARENT_CHIP = {short: 'RelZParent',
var MISSING_LAYER = {short: 'MissingLayer',
long: "This layer was referenced from the parent, but not present in the trace",
class: 'error'};
+var GPU_CHIP = {short: 'GPU',
+ long: "This layer was composed on the GPU",
+ class: 'gpu'};
+var HWC_CHIP = {short: 'HWC',
+ long: "This layer was composed by Hardware Composer",
+ class: 'hwc'};
-function transform_layer(layer, {parentBounds, parentHidden}) {
- function get_size(layer) {
- var size = layer.size || {w: 0, h: 0};
- return {
- left: 0,
- right: size.w,
- top: 0,
- bottom: size.h
- };
- }
-
- function get_crop(layer) {
- var crop = layer.crop || {left: 0, top: 0, right: 0 , bottom:0};
- return {
- left: crop.left || 0,
- right: crop.right || 0,
- top: crop.top || 0,
- bottom: crop.bottom || 0
- };
- }
-
- function intersect(bounds, crop) {
- return {
- left: Math.max(crop.left, bounds.left),
- right: Math.min(crop.right, bounds.right),
- top: Math.max(crop.top, bounds.top),
- bottom: Math.min(crop.bottom, bounds.bottom),
- };
- }
-
- function is_empty_rect(rect) {
- var right = rect.right || 0;
- var left = rect.left || 0;
- var top = rect.top || 0;
- var bottom = rect.bottom || 0;
-
- return (right - left) <= 0 || (bottom - top) <= 0;
- }
-
- function get_cropped_bounds(layer, parentBounds) {
- var size = get_size(layer);
- var crop = get_crop(layer);
- if (!is_empty_rect(size) && !is_empty_rect(crop)) {
- return intersect(size, crop);
- }
- if (!is_empty_rect(size)) {
- return size;
- }
- if (!is_empty_rect(crop)) {
- return crop;
- }
- return parentBounds || { left: 0, right: 0, top: 0, bottom: 0 };
- }
-
+function transform_layer(layer) {
function offset_to(bounds, x, y) {
return {
right: bounds.right - (bounds.left - x),
@@ -94,98 +43,30 @@ function transform_layer(layer, {parentBounds, parentHidden}) {
};
}
- function transform_bounds(layer, parentBounds) {
- var result = layer.bounds || get_cropped_bounds(layer, parentBounds);
- var tx = (layer.position) ? layer.position.x || 0 : 0;
- var ty = (layer.position) ? layer.position.y || 0 : 0;
+ function get_rect(layer) {
+ var result = layer.bounds;
+ var tx = layer.position ? layer.position.x || 0 : 0;
+ var ty = layer.position ? layer.position.y || 0 : 0;
result = offset_to(result, 0, 0);
result.label = layer.name;
- result.transform = layer.transform || {dsdx:1, dtdx:0, dsdy:0, dtdy:1};
+ result.transform = layer.transform;
result.transform.tx = tx;
result.transform.ty = ty;
return result;
}
- function is_opaque(layer) {
- return layer.color == undefined || (layer.color.a || 0) > 0;
- }
-
- function is_empty(region) {
- return region == undefined ||
- region.rect == undefined ||
- region.rect.length == 0 ||
- region.rect.every(function(r) { return is_empty_rect(r) } );
- }
-
- function is_rect_empty_and_valid(rect) {
- return rect &&
- (rect.left - rect.right === 0 || rect.top - rect.bottom === 0);
- }
-
- function is_transform_invalid(transform) {
- return !transform || (transform.dsdx * transform.dtdy ===
- transform.dtdx * transform.dsdy); //determinant of transform
- /**
- * The transformation matrix is defined as the product of:
- * | cos(a) -sin(a) | \/ | X 0 |
- * | sin(a) cos(a) | /\ | 0 Y |
- *
- * where a is a rotation angle, and X and Y are scaling factors.
- * A transformation matrix is invalid when either X or Y is zero,
- * as a rotation matrix is valid for any angle. When either X or Y
- * is 0, then the scaling matrix is not invertible, which makes the
- * transformation matrix not invertible as well. A 2D matrix with
- * components | A B | is uninvertible if and only if AD - BC = 0.
- * | C D |
- * This check is included above.
- */
- }
-
- /**
- * Checks if the layer is visible on screen according to its type,
- * active buffer content, alpha and visible regions.
- *
- * @param {layer} layer
- * @returns if the layer is visible on screen or not
- */
- function is_visible(layer) {
- var visible = (layer.activeBuffer || layer.type === 'ColorLayer')
- && !hidden && is_opaque(layer);
- visible &= !is_empty(layer.visibleRegion);
- return visible;
- }
-
- function postprocess_flags(layer) {
- if (!layer.flags) return;
- var verboseFlags = [];
- if (layer.flags & FLAG_HIDDEN) {
- verboseFlags.push("HIDDEN");
- }
- if (layer.flags & FLAG_OPAQUE) {
- verboseFlags.push("OPAQUE");
- }
- if (layer.flags & FLAG_SECURE) {
- verboseFlags.push("SECURE");
- }
-
- layer.flags = verboseFlags.join('|') + " (" + layer.flags + ")";
+ function add_hwc_composition_type_chip(layer) {
+ if (layer.hwcCompositionType === "CLIENT") {
+ chips.push(GPU_CHIP);
+ } else if (layer.hwcCompositionType === "DEVICE" || layer.hwcCompositionType === "SOLID_COLOR") {
+ chips.push(HWC_CHIP);
+ }
}
var chips = [];
- var rect = transform_bounds(layer, parentBounds);
- var hidden = (layer.flags & FLAG_HIDDEN) != 0 || parentHidden;
- var visible = is_visible(layer);
- if (visible) {
+ if (layer.visible) {
chips.push(get_visible_chip());
- } else {
- rect = undefined;
}
-
- var bounds = undefined;
- if (layer.name.startsWith("Display Root#0") && layer.sourceBounds) {
- bounds = {width: layer.sourceBounds.right, height: layer.sourceBounds.bottom};
- }
-
if ((layer.zOrderRelativeOf || -1) !== -1) {
chips.push(RELATIVE_Z_CHIP);
}
@@ -195,60 +76,22 @@ function transform_layer(layer, {parentBounds, parentHidden}) {
if (layer.missing) {
chips.push(MISSING_LAYER);
}
- function visibilityReason(layer) {
- var reasons = [];
- if (!layer.color || layer.color.a === 0) {
- reasons.push('Alpha is 0');
- }
- if (layer.flags && (layer.flags & FLAG_HIDDEN != 0)) {
- reasons.push('Flag is hidden');
- }
- if (is_rect_empty_and_valid(layer.crop)) {
- reasons.push('Crop is zero');
- }
- if (is_transform_invalid(layer.transform)) {
- reasons.push('Transform is invalid');
- }
- if (layer.isRelativeOf && layer.zOrderRelativeOf == -1) {
- reasons.push('RelativeOf layer has been removed');
- }
- return reasons.join();
- }
- if (parentHidden) {
- layer.invisibleDueTo = 'Hidden by parent with ID: ' + parentHidden;
- } else {
- let reasons_hidden = visibilityReason(layer);
- let isBufferLayer = (layer.type === 'BufferStateLayer' || layer.type === 'BufferQueueLayer');
- if (reasons_hidden) {
- layer.invisibleDueTo = reasons_hidden;
- parentHidden = layer.id
- } else if (layer.type === 'ContainerLayer') {
- layer.invisibleDueTo = 'This is a ContainerLayer.';
- } else if (isBufferLayer && (!layer.activeBuffer ||
- layer.activeBuffer.height === 0 || layer.activeBuffer.width === 0)) {
- layer.invisibleDueTo = 'The buffer is empty.';
- } else if (!visible) {
- layer.invisibleDueTo = 'Unknown. Occluded by another layer?';
- }
- }
- var transform_layer_with_parent_hidden =
- (layer) => transform_layer(layer, {parentBounds: rect, parentHidden: parentHidden});
- postprocess_flags(layer);
+ add_hwc_composition_type_chip(layer);
+ const rect = layer.visible ? get_rect(layer) : undefined;
+
return transform({
obj: layer,
kind: '',
name: layer.id + ": " + layer.name,
- children: [
- [layer.resolvedChildren, transform_layer_with_parent_hidden],
- ],
+ children: [[layer.resolvedChildren, transform_layer]],
rect,
- bounds,
+ undefined /* bounds */,
highlight: rect,
chips,
- visible,
+ visible: layer.visible,
});
}
-
+
function missingLayer(childId) {
return {
name: "layer #" + childId,
@@ -258,7 +101,7 @@ function missingLayer(childId) {
}
}
-function transform_layers(layers) {
+function transform_layers(includesCompositionState, layers) {
var idToItem = {};
var isChild = {}
@@ -282,7 +125,8 @@ function transform_layers(layers) {
});
var roots = layersList.filter((e) => !isChild[e.id]);
-
+ fill_inherited_state(idToItem, roots);
+ fill_occlusion_state(idToItem, roots, includesCompositionState);
function foreachTree(nodes, fun) {
nodes.forEach((n) => {
fun(n);
@@ -324,12 +168,13 @@ function transform_layers(layers) {
}
function transform_layers_entry(entry) {
+ const includesCompositionState = !entry.excludesCompositionState;
return transform({
obj: entry,
kind: 'entry',
name: nanos_to_string(entry.elapsedRealtimeNanos) + " - " + entry.where,
children: [
- [[entry.layers], transform_layers],
+ [[entry.layers], (layer) => transform_layers(includesCompositionState, layer)],
],
timestamp: entry.elapsedRealtimeNanos,
stableId: 'entry',
diff --git a/tools/winscope/src/transform_sys_ui.js b/tools/winscope/src/transform_sys_ui.js
new file mode 100644
index 000000000..f636b092c
--- /dev/null
+++ b/tools/winscope/src/transform_sys_ui.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2019, 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.
+ */
+
+import {transform, nanos_to_string, get_visible_chip} from './transform.js'
+
+function transform_edgeBack(edgeBack) {
+ return transform({
+ obj: edgeBack,
+ kind: 'edgeBack',
+ name: 'edgeBack',
+ children: []
+ });
+}
+
+function transform_systemUi(sysui) {
+ return transform({
+ obj: sysui,
+ kind: 'systemUi',
+ name: 'systemUi',
+ children: [
+ [[sysui.edgeBackGestureHandler], transform_edgeBack]
+ ]
+ });
+}
+
+function transform_entry(entry) {
+ return transform({
+ obj: entry,
+ kind: 'entry',
+ name: nanos_to_string(entry.elapsedRealtimeNanos),
+ children: [
+ [[entry.systemUi], transform_systemUi]
+ ],
+ timestamp: entry.elapsedRealtimeNanos,
+ stableId: 'entry'
+ });
+}
+
+function transform_sysui_trace(entries) {
+ return transform({
+ obj: entries,
+ kind: 'entries',
+ name: 'entries',
+ children: [
+ [entries.entry, transform_entry],
+ ],
+ });
+}
+
+export {transform_sysui_trace};
diff --git a/tools/winscope/src/transform_wm.js b/tools/winscope/src/transform_wm.js
index 85e60df44..8a947b61b 100644
--- a/tools/winscope/src/transform_wm.js
+++ b/tools/winscope/src/transform_wm.js
@@ -44,7 +44,8 @@ function transform_window(entry) {
kind: 'window',
name,
children: [
- [entry.childWindows, transform_window]
+ [entry.childWindows, transform_window],
+ [entry.windowContainer.children.reverse(), transform_window_container_child],
],
rect,
highlight: rect,
@@ -53,13 +54,14 @@ function transform_window(entry) {
});
}
-function transform_app_window_token(entry) {
+function transform_activity_record(entry) {
return transform({
obj: entry,
- kind: 'appWinToken',
+ kind: 'activityRecord',
name: entry.name,
children: [
[entry.windowToken.windows, transform_window],
+ [entry.windowToken.windowContainer.children.reverse(), transform_window_container_child],
],
});
}
@@ -70,7 +72,9 @@ function transform_task(entry) {
kind: 'task',
name: entry.id || 0,
children: [
- [entry.appWindowTokens, transform_app_window_token],
+ [entry.tasks, transform_task],
+ [entry.activities, transform_activity_record],
+ [entry.windowContainer.children.reverse(), transform_window_container_child],
],
});
}
@@ -93,6 +97,7 @@ function transform_window_token(entry) {
name: '',
children: [
[entry.windows, transform_window],
+ [entry.windowContainer.children.reverse(), transform_window_container_child],
],
});
}
@@ -130,7 +135,36 @@ function transform_ime(entry) {
});
}
-function transform_display(entry) {
+function transform_window_container_child(entry) {
+ if (entry.displayArea != null) {return transform_display_area(entry.displayArea)}
+ if (entry.displayContent != null) {return transform_display_content(entry.displayContent)}
+ if (entry.task != null) {return transform_task(entry.task)}
+ if (entry.activity != null) {return transform_activity_record(entry.activity)}
+ if (entry.windowToken != null) {return transform_window_token(entry.windowToken)}
+ if (entry.window != null) {return transform_window(entry.window)}
+
+ // The WindowContainerChild may be unknown
+ return transform({
+ obj: entry,
+ kind: 'WindowContainerChild',
+ name: '',
+ children: [[entry.windowContainer.children.reverse(), transform_window_container_child],]
+ });
+}
+
+
+function transform_display_area(entry) {
+ return transform({
+ obj: entry,
+ kind: 'DisplayArea',
+ name: entry.name,
+ children: [
+ [entry.windowContainer.children.reverse(), transform_window_container_child],
+ ],
+ });
+}
+
+function transform_display_content(entry) {
var bounds = {
width: entry.displayInfo.logicalWidth || 0,
height: entry.displayInfo.logicalHeight || 0,
@@ -144,7 +178,9 @@ function transform_display(entry) {
[entry.aboveAppWindows, transform_above],
[entry.imeWindows, transform_ime],
[entry.stacks, transform_stack],
+ [entry.tasks, transform_task],
[entry.belowAppWindows, transform_below],
+ [entry.windowContainer.children.reverse(), transform_window_container_child],
],
bounds,
});
@@ -165,8 +201,10 @@ function transform_window_service(entry) {
kind: 'service',
name: '',
children: [
- [entry.rootWindowContainer.displays, transform_display],
- [[entry.policy], transform_policy],
+ [entry.rootWindowContainer.displays, transform_display_content],
+ [entry.rootWindowContainer.windowContainer.children.reverse(),
+ transform_window_container_child],
+ [[entry.policy], transform_policy],
],
timestamp: entry.elapsedRealtimeNanos,
});
@@ -178,7 +216,9 @@ function transform_entry(entry) {
kind: 'entry',
name: nanos_to_string(entry.elapsedRealtimeNanos),
children: [
- [entry.windowManagerService.rootWindowContainer.displays, transform_display],
+ [entry.windowManagerService.rootWindowContainer.displays, transform_display_content],
+ [entry.windowManagerService.rootWindowContainer.windowContainer.children.reverse(),
+ transform_window_container_child],
[[entry.windowManagerService.policy], transform_policy],
],
timestamp: entry.elapsedRealtimeNanos,
diff --git a/tools/winscope/tests/samples/wl_trace.pb b/tools/winscope/tests/samples/wl_trace.pb
new file mode 100644
index 000000000..7e1f0075e
Binary files /dev/null and b/tools/winscope/tests/samples/wl_trace.pb differ
diff --git a/tools/winscope/webpack.config.js b/tools/winscope/webpack.config.js
index 3a1b92db7..ff31ff361 100644
--- a/tools/winscope/webpack.config.js
+++ b/tools/winscope/webpack.config.js
@@ -14,32 +14,18 @@
* limitations under the License.
*/
+const fs = require('fs');
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin')
function getWaylandSafePath() {
- if (process.env.AOSP) {
- return path.resolve(__dirname, 'src/stubs');
+ waylandPath = path.resolve(__dirname, '../../../vendor/google_arc/libs/wayland_service');
+ if (fs.existsSync(waylandPath)) {
+ return waylandPath;
}
- return path.resolve(__dirname, '../../../vendor/google_arc/libs/wayland_service');
-}
-
-// b/148409169 remove once proto log support is in AOSP.
-function getProtoLogSafePath() {
- if (process.env.AOSP) {
- return path.resolve(__dirname, 'src/stubs');
- }
- return path.resolve(__dirname, '../../../frameworks/base/core/proto/android/server');
-}
-
-// b/148409169 remove once proto log support is in AOSP.
-function getProtoLogJsonSafePath() {
- if (process.env.AOSP) {
- return path.resolve(__dirname, 'src/stubs');
- }
- return path.resolve(__dirname, '../../../frameworks/base/data/etc');
+ return path.resolve(__dirname, 'src/stubs');
}
module.exports = {
@@ -86,8 +72,6 @@ module.exports = {
resolve: {
alias: {
WaylandSafePath: getWaylandSafePath(),
- ProtoLogSafePath: getProtoLogSafePath(),
- ProtoLogJsonSafePath: getProtoLogJsonSafePath(),
},
modules: [
'node_modules',
diff --git a/vndk/tools/definition-tool/datasets/vndk-lib-extra-list-30.txt b/vndk/tools/definition-tool/datasets/vndk-lib-extra-list-30.txt
new file mode 100644
index 000000000..38d4bce4a
--- /dev/null
+++ b/vndk/tools/definition-tool/datasets/vndk-lib-extra-list-30.txt
@@ -0,0 +1,45 @@
+FWK-ONLY-RS: libft2.so
+FWK-ONLY-RS: libmediandk.so
+
+LLNDK: libclang_rt.asan-aarch64-android.so
+LLNDK: libclang_rt.asan-arm-android.so
+LLNDK: libclang_rt.asan-i686-android.so
+LLNDK: libclang_rt.asan-mips-android.so
+LLNDK: libclang_rt.asan-mips64-android.so
+LLNDK: libclang_rt.asan-x86_64-android.so
+
+VNDK-core: libclang_rt.scudo-aarch64-android.so
+VNDK-core: libclang_rt.scudo-arm-android.so
+VNDK-core: libclang_rt.scudo-i686-android.so
+VNDK-core: libclang_rt.scudo-x86_64-android.so
+
+VNDK-core: libclang_rt.scudo_minimal-aarch64-android.so
+VNDK-core: libclang_rt.scudo_minimal-arm-android.so
+VNDK-core: libclang_rt.scudo_minimal-i686-android.so
+VNDK-core: libclang_rt.scudo_minimal-x86_64-android.so
+
+VNDK-core: libclang_rt.ubsan_standalone-aarch64-android.so
+VNDK-core: libclang_rt.ubsan_standalone-arm-android.so
+VNDK-core: libclang_rt.ubsan_standalone-i686-android.so
+VNDK-core: libclang_rt.ubsan_standalone-mips-android.so
+VNDK-core: libclang_rt.ubsan_standalone-mips64-android.so
+VNDK-core: libclang_rt.ubsan_standalone-x86_64-android.so
+
+LLNDK-private: ld-android.so
+LLNDK-private: libc_malloc_debug.so
+LLNDK-private: libdl_android.so
+LLNDK-private: libnetd_client.so
+LLNDK-private: libtextclassifier_hash.so
+
+# Same-Process HAL implementations
+SP-HAL: [regex]^.*/android\.hardware\.graphics\.mapper@\d+\.\d+-impl\.so$
+SP-HAL: [regex]^.*/android\.hardware\.renderscript@1\.0-impl\.so$
+SP-HAL: [regex]^.*/gralloc\..*\.so$
+SP-HAL: [regex]^/vendor/.*/libEGL_.*\.so$
+SP-HAL: [regex]^/vendor/.*/libGLES_.*\.so$
+SP-HAL: [regex]^/vendor/.*/libGLESv1_CM_.*\.so$
+SP-HAL: [regex]^/vendor/.*/libGLESv2_.*\.so$
+SP-HAL: [regex]^/vendor/.*/libGLESv3_.*\.so$
+SP-HAL: [regex]^/vendor/.*/libPVRRS\.so$
+SP-HAL: [regex]^/vendor/.*/libRSDriver.*\.so$
+SP-HAL: [regex]^/vendor/.*/vulkan.*\.so$