Android12 双屏异显/异触流程分析
一、异显apk示例代码
安卓12系统源码的ApiDemo的示例代码:development/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java
该demo主要演示presentation的用法,presentation是一种特殊的对话框,主要用于在另外一块屏幕上显示内容。值得注意的是在创建presentation前必须把presentation与它的目标屏幕相关联,所以在使用它之前必须为它选择一个Display,选择display的方法主要有两种:
(1)利用MediaRouter
mMediaRouter = (MediaRouter)getSystemService(Context.MEDIA_ROUTER_SERVICE);
MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO);
Display presentation = route != null ? route.getPresentationDisplay() :null;
presentation.show();
presentation.setOnDismissListener(mOnDismissListener);
(2)利用DisplayManager,也是ApiDemo的实现方式:
其中DisplayListAdapter 是 listView 的适配器,用于显示所有的屏幕信息,可获得对应DisplayId
/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.apis.app; // Need the following import to get access to the app resources, since this // class is in a sub-package. import com.example.android.apis.R; import android.app.Activity; import android.app.AlertDialog; import android.app.Presentation; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.graphics.Point; import android.graphics.drawable.GradientDrawable; import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable.Creator; import android.util.Log; import android.util.SparseArray; import android.view.Display; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; //BEGIN_INCLUDE(activity) /** * <h3>Presentation Activity</h3> * * <p> * This demonstrates how to create an activity that shows some content * on a secondary display using a {@link Presentation}. * </p><p> * The activity uses the {@link DisplayManager} API to enumerate displays. * When the user selects a display, the activity opens a {@link Presentation} * on that display. We show a different photograph in each presentation * on a unique background along with a label describing the display. * We also write information about displays and display-related events to * the Android log which you can read using <code>adb logcat</code>. * </p><p> * You can try this out using an HDMI or Wifi display or by using the * "Simulate secondary displays" feature in Development Settings to create a few * simulated secondary displays. Each display will appear in the list along with a * checkbox to show a presentation on that display. * </p><p> * See also the {@link PresentationWithMediaRouterActivity} sample which * uses the media router to automatically select a secondary display * on which to show content based on the currently selected route. * </p> */ public class PresentationActivity extends Activity implements OnCheckedChangeListener, OnClickListener, OnItemSelectedListener { private final String TAG = "PresentationActivity"; // Key for storing saved instance state. private static final String PRESENTATION_KEY = "presentation"; // The content that we want to show on the presentation. private static final int[] PHOTOS = new int[] { R.drawable.frantic, R.drawable.photo1, R.drawable.photo2, R.drawable.photo3, R.drawable.photo4, R.drawable.photo5, R.drawable.photo6, R.drawable.sample_4, }; private DisplayManager mDisplayManager; private DisplayListAdapter mDisplayListAdapter; private CheckBox mShowAllDisplaysCheckbox; private ListView mListView; private int mNextImageNumber; // List of presentation contents indexed by displayId. // This state persists so that we can restore the old presentation // contents when the activity is paused or resumed. private SparseArray<DemoPresentationContents> mSavedPresentationContents; // List of all currently visible presentations indexed by display id. private final SparseArray<DemoPresentation> mActivePresentations = new SparseArray<DemoPresentation>(); /** * Initialization of the Activity after it is first created. Must at least * call {@link android.app.Activity#setContentView setContentView()} to * describe what is to be displayed in the screen. */ @Override protected void onCreate(Bundle savedInstanceState) { // Be sure to call the super class. super.onCreate(savedInstanceState); // Restore saved instance state. if (savedInstanceState != null) { mSavedPresentationContents = savedInstanceState.getSparseParcelableArray(PRESENTATION_KEY); } else { mSavedPresentationContents = new SparseArray<DemoPresentationContents>(); } // Get the display manager service. mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE); // See assets/res/any/layout/presentation_activity.xml for this // view layout definition, which is being set here as // the content of our screen. setContentView(R.layout.presentation_activity); // Set up checkbox to toggle between showing all displays or only presentation displays. mShowAllDisplaysCheckbox = (CheckBox)findViewById(R.id.show_all_displays); mShowAllDisplaysCheckbox.setOnCheckedChangeListener(this); // Set up the list of displays. mDisplayListAdapter = new DisplayListAdapter(this); mListView = (ListView)findViewById(R.id.display_list); mListView.setAdapter(mDisplayListAdapter); } @Override protected void onResume() { // Be sure to call the super class. super.onResume(); // Update our list of displays on resume. mDisplayListAdapter.updateContents(); // Restore presentations from before the activity was paused. final int numDisplays = mDisplayListAdapter.getCount(); for (int i = 0; i < numDisplays; i++) { final Display display = mDisplayListAdapter.getItem(i); final DemoPresentationContents contents = mSavedPresentationContents.get(display.getDisplayId()); if (contents != null) { showPresentation(display, contents); } } mSavedPresentationContents.clear(); // Register to receive events from the display manager. mDisplayManager.registerDisplayListener(mDisplayListener, null); } @Override protected void onPause() { // Be sure to call the super class. super.onPause(); // Unregister from the display manager. mDisplayManager.unregisterDisplayListener(mDisplayListener); // Dismiss all of our presentations but remember their contents. Log.d(TAG, "Activity is being paused. Dismissing all active presentation."); for (int i = 0; i < mActivePresentations.size(); i++) { DemoPresentation presentation = mActivePresentations.valueAt(i); int displayId = mActivePresentations.keyAt(i); mSavedPresentationContents.put(displayId, presentation.mContents); presentation.dismiss(); } mActivePresentations.clear(); } @Override protected void onSaveInstanceState(Bundle outState) { // Be sure to call the super class. super.onSaveInstanceState(outState); outState.putSparseParcelableArray(PRESENTATION_KEY, mSavedPresentationContents); } /** * Shows a {@link Presentation} on the specified display. */ private void showPresentation(Display display, DemoPresentationContents contents) { final int displayId = display.getDisplayId(); if (mActivePresentations.get(displayId) != null) { return; } Log.d(TAG, "Showing presentation photo #" + contents.photo + " on display #" + displayId + "."); DemoPresentation presentation = new DemoPresentation(this, display, contents); presentation.show(); presentation.setOnDismissListener(mOnDismissListener); mActivePresentations.put(displayId, presentation); } /** * Hides a {@link Presentation} on the specified display. */ private void hidePresentation(Display display) { final int displayId = display.getDisplayId(); DemoPresentation presentation = mActivePresentations.get(displayId); if (presentation == null) { return; } Log.d(TAG, "Dismissing presentation on display #" + displayId + "."); presentation.dismiss(); mActivePresentations.delete(displayId); } /** * Sets the display mode of the {@link Presentation} on the specified display * if it is already shown. */ private void setPresentationDisplayMode(Display display, int displayModeId) { final int displayId = display.getDisplayId(); DemoPresentation presentation = mActivePresentations.get(displayId); if (presentation == null) { return; } presentation.setPreferredDisplayMode(displayModeId); } private int getNextPhoto() { final int photo = mNextImageNumber; mNextImageNumber = (mNextImageNumber + 1) % PHOTOS.length; return photo; } /** * Called when the show all displays checkbox is toggled or when * an item in the list of displays is checked or unchecked. */ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (buttonView == mShowAllDisplaysCheckbox) { // Show all displays checkbox was toggled. mDisplayListAdapter.updateContents(); } else { // Display item checkbox was toggled. final Display display = (Display)buttonView.getTag(); if (isChecked) { DemoPresentationContents contents = new DemoPresentationContents(getNextPhoto()); showPresentation(display, contents); } else { hidePresentation(display); } mDisplayListAdapter.updateContents(); } } /** * Called when the Info button next to a display is clicked to show information * about the display. */ @Override public void onClick(View v) { Context context = v.getContext(); AlertDialog.Builder builder = new AlertDialog.Builder(context); final Display display = (Display)v.getTag(); Resources r = context.getResources(); AlertDialog alert = builder .setTitle(r.getString( R.string.presentation_alert_info_text, display.getDisplayId())) .setMessage(display.toString()) .setNeutralButton(R.string.presentation_alert_dismiss_text, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .create(); alert.show(); } /** * Called when a display mode has been selected. */ @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { final Display display = (Display)parent.getTag(); final Display.Mode[] modes = display.getSupportedModes(); setPresentationDisplayMode(display, position >= 1 && position <= modes.length ? modes[position - 1].getModeId() : 0); } /** * Called when a display mode has been unselected. */ @Override public void onNothingSelected(AdapterView<?> parent) { final Display display = (Display)parent.getTag(); setPresentationDisplayMode(display, 0); } /** * Listens for displays to be added, changed or removed. * We use it to update the list and show a new {@link Presentation} when a * display is connected. * * Note that we don't bother dismissing the {@link Presentation} when a * display is removed, although we could. The presentation API takes care * of doing that automatically for us. */ private final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { @Override public void onDisplayAdded(int displayId) { Log.d(TAG, "Display #" + displayId + " added."); mDisplayListAdapter.updateContents(); } @Override public void onDisplayChanged(int displayId) { Log.d(TAG, "Display #" + displayId + " changed."); mDisplayListAdapter.updateContents(); } @Override public void onDisplayRemoved(int displayId) { Log.d(TAG, "Display #" + displayId + " removed."); mDisplayListAdapter.updateContents(); } }; /** * Listens for when presentations are dismissed. */ private final DialogInterface.OnDismissListener mOnDismissListener = new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { DemoPresentation presentation = (DemoPresentation)dialog; int displayId = presentation.getDisplay().getDisplayId(); Log.d(TAG, "Presentation on display #" + displayId + " was dismissed."); mActivePresentations.delete(displayId); mDisplayListAdapter.notifyDataSetChanged(); } }; /** * List adapter. * Shows information about all displays. */ private final class DisplayListAdapter extends ArrayAdapter<Display> { final Context mContext; public DisplayListAdapter(Context context) { super(context, R.layout.presentation_list_item); mContext = context; } @Override public View getView(int position, View convertView, ViewGroup parent) { final View v; if (convertView == null) { v = ((Activity) mContext).getLayoutInflater().inflate( R.layout.presentation_list_item, null); } else { v = convertView; } final Display display = getItem(position); final int displayId = display.getDisplayId(); DemoPresentation presentation = mActivePresentations.get(displayId); DemoPresentationContents contents = presentation != null ? presentation.mContents : null; if (contents == null) { contents = mSavedPresentationContents.get(displayId); } CheckBox cb = (CheckBox)v.findViewById(R.id.checkbox_presentation); cb.setTag(display); cb.setOnCheckedChangeListener(PresentationActivity.this); cb.setChecked(contents != null); cb.setEnabled((display.getFlags() & Display.FLAG_PRESENTATION) != 0); TextView tv = (TextView)v.findViewById(R.id.display_id); tv.setText(v.getContext().getResources().getString( R.string.presentation_display_id_text, displayId, display.getName())); Button b = (Button)v.findViewById(R.id.info); b.setTag(display); b.setOnClickListener(PresentationActivity.this); Spinner s = (Spinner)v.findViewById(R.id.modes); Display.Mode[] modes = display.getSupportedModes(); if (contents == null || modes.length == 1) { s.setVisibility(View.GONE); s.setAdapter(null); } else { ArrayAdapter<String> modeAdapter = new ArrayAdapter<String>(mContext, android.R.layout.simple_list_item_1); s.setVisibility(View.VISIBLE); s.setAdapter(modeAdapter); s.setTag(display); s.setOnItemSelectedListener(PresentationActivity.this); modeAdapter.add("<default mode>"); for (Display.Mode mode : modes) { modeAdapter.add(String.format("Mode %d: %dx%d/%.1ffps", mode.getModeId(), mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate())); if (contents.displayModeId == mode.getModeId()) { s.setSelection(modeAdapter.getCount() - 1); } } } return v; } /** * Update the contents of the display list adapter to show * information about all current displays. */ public void updateContents() { clear(); String displayCategory = getDisplayCategory(); Display[] displays = mDisplayManager.getDisplays(displayCategory); addAll(displays); Log.d(TAG, "There are currently " + displays.length + " displays connected."); for (Display display : displays) { Log.d(TAG, " " + display); } } private String getDisplayCategory() { return mShowAllDisplaysCheckbox.isChecked() ? null : DisplayManager.DISPLAY_CATEGORY_PRESENTATION; } } /** * The presentation to show on the secondary display. * * Note that the presentation display may have different metrics from the display on which * the main activity is showing so we must be careful to use the presentation's * own {@link Context} whenever we load resources. */ private final class DemoPresentation extends Presentation { final DemoPresentationContents mContents; public DemoPresentation(Context context, Display display, DemoPresentationContents contents) { super(context, display); mContents = contents; } /** * Sets the preferred display mode id for the presentation. */ public void setPreferredDisplayMode(int modeId) { mContents.displayModeId = modeId; WindowManager.LayoutParams params = getWindow().getAttributes(); params.preferredDisplayModeId = modeId; getWindow().setAttributes(params); } @Override protected void onCreate(Bundle savedInstanceState) { // Be sure to call the super class. super.onCreate(savedInstanceState); // Get the resources for the context of the presentation. // Notice that we are getting the resources from the context of the presentation. Resources r = getContext().getResources(); // Inflate the layout. setContentView(R.layout.presentation_content); final Display display = getDisplay(); final int displayId = display.getDisplayId(); final int photo = mContents.photo; // Show a caption to describe what's going on. TextView text = (TextView)findViewById(R.id.text); text.setText(r.getString(R.string.presentation_photo_text, photo, displayId, display.getName())); // Show a n image for visual interest. ImageView image = (ImageView)findViewById(R.id.image); image.setImageDrawable(r.getDrawable(PHOTOS[photo])); GradientDrawable drawable = new GradientDrawable(); drawable.setShape(GradientDrawable.RECTANGLE); drawable.setGradientType(GradientDrawable.RADIAL_GRADIENT); // Set the background to a random gradient. Point p = new Point(); getDisplay().getSize(p); drawable.setGradientRadius(Math.max(p.x, p.y) / 2); drawable.setColors(mContents.colors); findViewById(android.R.id.content).setBackground(drawable); } } /** * Information about the content we want to show in the presentation. */ private final static class DemoPresentationContents implements Parcelable { final int photo; final int[] colors; int displayModeId; public static final Creator<DemoPresentationContents> CREATOR = new Creator<DemoPresentationContents>() { @Override public DemoPresentationContents createFromParcel(Parcel in) { return new DemoPresentationContents(in); } @Override public DemoPresentationContents[] newArray(int size) { return new DemoPresentationContents[size]; } }; public DemoPresentationContents(int photo) { this.photo = photo; colors = new int[] { ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000, ((int) (Math.random() * Integer.MAX_VALUE)) | 0xFF000000 }; } private DemoPresentationContents(Parcel in) { photo = in.readInt(); colors = new int[] { in.readInt(), in.readInt() }; displayModeId = in.readInt(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(photo); dest.writeInt(colors[0]); dest.writeInt(colors[1]); dest.writeInt(displayModeId); } } } //END_INCLUDE(activity)
二、Framework层Presentation及show方法
1.Presenation类:public class Presentation extends Dialog
/** * Creates a new presentation that is attached to the specified display * using the optionally specified theme, and override the default window type for the * presentation. * @param outerContext The context of the application that is showing the presentation. * The presentation will create its own context (see {@link #getContext()}) based * on this context and information about the associated display. * From {@link android.os.Build.VERSION_CODES#S}, the presentation will create its own window * context based on this context, information about the associated display and the window type. * If the window type is not specified, the presentation will choose the default type for the * presentation. * @param display The display to which the presentation should be attached. * @param theme A style resource describing the theme to use for the window. * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes"> * Style and Theme Resources</a> for more information about defining and using * styles. This theme is applied on top of the current theme in * <var>outerContext</var>. If 0, the default presentation theme will be used. * @param type Window type. * * @hide */ public Presentation(@NonNull Context outerContext, @NonNull Display display, int theme, @WindowType int type) { super(createPresentationContext(outerContext, display, theme, type), theme, false); mDisplay = display; mDisplayManager = getContext().getSystemService(DisplayManager.class); final Window w = getWindow(); final WindowManager.LayoutParams attr = w.getAttributes(); w.setAttributes(attr); w.setGravity(Gravity.FILL); w.setType(getWindowType(type, display)); setCanceledOnTouchOutside(false); }
private static Context createPresentationContext( Context outerContext, Display display, int theme, @WindowType int type) { if (outerContext == null) { throw new IllegalArgumentException("outerContext must not be null"); } if (display == null) { throw new IllegalArgumentException("display must not be null"); } Context windowContext = outerContext.createDisplayContext(display) .createWindowContext(getWindowType(type, display), null /* options */); if (theme == 0) { TypedValue outValue = new TypedValue(); windowContext.getTheme().resolveAttribute( com.android.internal.R.attr.presentationTheme, outValue, true); theme = outValue.resourceId; } return new ContextThemeWrapper(windowContext, theme); }
Presentation继承自Dialog,获取到Presentation要显示的设备后,就要将Activity的context对象和设备信息作为参数来创建Presentation对象,将设备记录在成员变量mDisplay中,将Presentation设置为不可在外部点击取消。
show流程图:
创建ViewRoot并将view添加到链表上:
/** * We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) { synchronized (this) { if (mView == null) { mView = view; ......try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); adjustLayoutParamsForCompatibility(mWindowAttributes); controlInsetsForCompatibility(mWindowAttributes); res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets, mTempControls); if (mTranslator != null) { mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets); mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls); } } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } }
......
} } }
setView的作用主要有两个:
(1)创建InputChannel来接受输入事件。
(2)将Window加入到WindowManager。
三、输入事件的传递
通过 frameworks/native/services/inputflinger/reader/mapper/ 下的mapper类可以看出,安卓将输入设备分为以下几种类型:
CursorInputMapper :鼠标 ExternalStylusInputMapper : 触控笔 JoystickInputMapper :游戏杆 KeyboardInputMapper :键盘 KeyMouseInputMapper :通常由一个手持设备组成,具有键盘和触控板/鼠标的功能,适用于需要键盘输入和鼠标操作的情况,如在移动设备上进行文字输入和浏览。 RotaryEncoderInputMapper :旋转编码器输入设备,一种用于测量旋转运动的设备。 SwitchInputMapper : 开关 TouchInputMapper 、MultiTouchInputMapper、SingleTouchInputMapper:触摸屏 VibratorInputMapper :震动器,严格意义上是输出设备
input设备不同初始化参数也不一样,如触摸设备的话可以配置displayId,Android12之前版本鼠标则只能输出到主显,而Android12可以通过 setprop sys.mouse.presentation 1 使能鼠标用于副屏,具体见CursorInputMapper.cpp的实现:
char mMousePresentation[PROPERTY_VALUE_MAX] = {0}; property_get("sys.mouse.presentation", mMousePresentation, "0"); if (strcmp(mMousePresentation, "1") == 0) { //displayId = mDisplayId; float minX, minY, maxX, maxY; if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { if(xCursorPosition==minX||xCursorPosition==maxX||yCursorPosition==minY||yCursorPosition==maxY){ displayId=getPolicy()->notifyDisplayIdChanged(); //mDisplayId=displayId; } } }else{ displayId = mPointerController->getDisplayId(); } } else { // Pointer capture and navigation modes pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY); }
touch.displayId的配置流程:
void TouchInputMapper::configureParameters() { // Use the pointer presentation mode for devices that do not support distinct // multitouch. The spot-based presentation relies on being able to accurately // locate two or more fingers on the touch pad. mParameters.gestureMode = getDeviceContext().hasInputProperty(INPUT_PROP_SEMI_MT) ? Parameters::GestureMode::SINGLE_TOUCH : Parameters::GestureMode::MULTI_TOUCH; ...... mParameters.hasAssociatedDisplay = false; mParameters.associatedDisplayIsExternal = false; if (mParameters.orientationAware || mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN || mParameters.deviceType == Parameters::DeviceType::POINTER) { mParameters.hasAssociatedDisplay = true; if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) { mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal(); String8 uniqueDisplayId; getDeviceContext().getConfiguration().tryGetProperty(String8("touch.displayId"), uniqueDisplayId); mParameters.uniqueDisplayId = uniqueDisplayId.c_str(); } } if (getDeviceContext().getAssociatedDisplayPort()) { mParameters.hasAssociatedDisplay = true; } // Initial downs on external touch devices should wake the device. // Normally we don't do this for internal touch screens to prevent them from waking // up in your pocket but you can enable it using the input device configuration. mParameters.wake = getDeviceContext().isExternal(); getDeviceContext().getConfiguration().tryGetProperty(String8("touch.wake"), mParameters.wake); }
touch.displayId的传递流程:
void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) { PointerCoords pointerCoords[MAX_POINTERS]; PointerProperties pointerProperties[MAX_POINTERS]; uint32_t pointerCount = 0;
......
float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; if (mDeviceMode == DeviceMode::POINTER) { auto [x, y] = getMouseCursorPosition(); xCursorPosition = x; yCursorPosition = y; } const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE); const int32_t deviceId = getDeviceId(); std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames(); std::for_each(frames.begin(), frames.end(), [this](TouchVideoFrame& frame) { frame.rotate(this->mSurfaceOrientation); }); NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId, policyFlags, action, actionButton, flags, metaState, buttonState, MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties, pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition, downTime, std::move(frames)); getListener()->notifyMotion(&args); }
将事件压入队列,具体实现见:frameworks/native/services/inputflinger/InputListener.cpp
void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) { traceEvent(__func__, args->id); mArgsQueue.push_back(new NotifyMotionArgs(*args)); }
然后在InputReader的loopOnce中会进行通知,具体实现见:frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() { int32_t oldGeneration; int32_t timeoutMillis; bool inputDevicesChanged = false; std::vector<InputDeviceInfo> inputDevices; { // acquire lock std::scoped_lock _l(mLock); oldGeneration = mGeneration; timeoutMillis = -1; uint32_t changes = mConfigurationChangesToRefresh; if (changes) { mConfigurationChangesToRefresh = 0; timeoutMillis = 0; refreshConfigurationLocked(changes); } else if (mNextTimeout != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout); } } // release lock size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); { // acquire lock std::scoped_lock _l(mLock); mReaderIsAliveCondition.notify_all(); if (count) { processEventsLocked(mEventBuffer, count); } if (mNextTimeout != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); if (now >= mNextTimeout) { #if DEBUG_RAW_EVENTS ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); #endif mNextTimeout = LLONG_MAX; timeoutExpiredLocked(now); } } if (oldGeneration != mGeneration) { inputDevicesChanged = true; inputDevices = getInputDevicesLocked(); } } // release lock // Send out a message that the describes the changed input devices. if (inputDevicesChanged) { mPolicy->notifyInputDevicesChanged(inputDevices); } // Flush queued events out to the listener. // This must happen outside of the lock because the listener could potentially call // back into the InputReader's methods, such as getScanCodeState, or become blocked // on another thread similarly waiting to acquire the InputReader lock thereby // resulting in a deadlock. This situation is actually quite plausible because the // listener is actually the input dispatcher, which calls into the window manager, // which occasionally calls into the input reader. mQueuedListener->flush(); }
在InputDispatcher中同样是用的一个线程找到对应的窗口进行事件分发,具体实现见:frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::dispatchOnce() { nsecs_t nextWakeupTime = LONG_LONG_MAX; { // acquire lock std::scoped_lock _l(mLock); mDispatcherIsAlive.notify_all(); // Run a dispatch loop if there are no pending commands. // The dispatch loop might enqueue commands to run afterwards. if (!haveCommandsLocked()) { dispatchOnceInnerLocked(&nextWakeupTime); } // Run all pending commands if there are any. // If any commands were run then force the next poll to wake up immediately. if (runCommandsLockedInterruptible()) { nextWakeupTime = LONG_LONG_MIN; } // If we are still waiting for ack on some events, // we might have to wake up earlier to check if an app is anr'ing. const nsecs_t nextAnrCheck = processAnrsLocked(); nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck); // We are about to enter an infinitely long sleep, because we have no commands or // pending or queued events if (nextWakeupTime == LONG_LONG_MAX) { mDispatcherEnteredIdle.notify_all(); } } // release lock // Wait for callback or timeout or wake. (make sure we round up, not down) nsecs_t currentTime = now(); int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime); mLooper->pollOnce(timeoutMillis); }
对应EventEntry::Type::MOTION类型事件处理在 InputDispatcher::dispatchMotionLocked 方法中,根据entry->displayId决定分发到哪个屏幕:
bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { ATRACE_CALL(); // Preprocessing. if (!entry->dispatchInProgress) { entry->dispatchInProgress = true; logOutboundMotionDetails("dispatchMotion - ", *entry); } // Clean up if dropping the event. if (*dropReason != DropReason::NOT_DROPPED) { setInjectionResult(*entry, *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED : InputEventInjectionResult::FAILED); return true; } bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER; // Identify targets. std::vector<InputTarget> inputTargets; bool conflictingPointerActions = false; InputEventInjectionResult injectionResult; if (isPointerEvent) { // Pointer event. (eg. touchscreen) injectionResult = findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime, &conflictingPointerActions); } else { // Non touch event. (eg. trackball) injectionResult = findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime); } if (injectionResult == InputEventInjectionResult::PENDING) { return false; } setInjectionResult(*entry, injectionResult); if (injectionResult == InputEventInjectionResult::PERMISSION_DENIED) { ALOGW("Permission denied, dropping the motion (isPointer=%s)", toString(isPointerEvent)); return true; } if (injectionResult != InputEventInjectionResult::SUCCEEDED) { CancelationOptions::Mode mode(isPointerEvent ? CancelationOptions::CANCEL_POINTER_EVENTS : CancelationOptions::CANCEL_NON_POINTER_EVENTS); CancelationOptions options(mode, "input event injection failed"); synthesizeCancelationEventsForMonitorsLocked(options); return true; } // Add monitor channels from event's or focused display. addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry)); if (isPointerEvent) { std::unordered_map<int32_t, TouchState>::iterator it = mTouchStatesByDisplay.find(entry->displayId); if (it != mTouchStatesByDisplay.end()) { const TouchState& state = it->second; if (!state.portalWindows.empty()) { // The event has gone through these portal windows, so we add monitoring targets of // the corresponding displays as well. for (size_t i = 0; i < state.portalWindows.size(); i++) { const WindowInfo* windowInfo = state.portalWindows[i]->getInfo(); addGlobalMonitoringTargetsLocked(inputTargets, windowInfo->portalToDisplayId, -windowInfo->frameLeft, -windowInfo->frameTop); } } } } // Dispatch the motion. if (conflictingPointerActions) { CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, "conflicting pointer actions"); synthesizeCancelationEventsForAllConnectionsLocked(options); } dispatchEventLocked(currentTime, entry, inputTargets); return true; }
Motion事件分发过程,简化为下图:
从软件流程分析,touchScreen类型可以通过配置device.internal来配置TP事件输出到辅显,而Pointer类型的话则是通过displayId去设置输出的方向。
idc(Input Device Configuration)为输入设备配置文件,包含设备具体的配置属性,这些属性影响输入设备的行为,常见的配置有:
device.internal 指定输入设备属于内置组件;还是外部链接(很可能拆卸)的外围设备;0表示外部,1表示内部 touch.deviceType touchScreen(与显示屏相关的触摸屏),touchPad(不与显示相关连的触摸板),touchNavigation,pointer(类似于鼠标),default(系统根据分类算法自动检测设备类型) touch.orientationAware 等于1时表示触摸会随着显示屏方向更改,为0则表示不受显示屏方向更改的影响 touch.gestureMode single-touch multi-touch default touch.wake tp是否需要唤醒系统,一般希望外部设备才有这种能力,如果是内部的设备需要唤醒系统,也可以进行配置 ...
另外google也有使用xml指定哪些输入设备属于哪些屏幕,这种关联由端口号完成,其中端口是指与屏幕连接的物理端口。
例如,如果 Android 设备有两个标记为 hdmi1
和 hdmi2
的 HDMI 端口,则屏幕端口值可以为 1
和 2
。即使将不同的屏幕(例如不同的屏幕型号或制造商)连接到同一物理 HDMI 端口,端口值也保持不变。这样一来,设备制造商就能够提供组装和升级屏幕的说明。
这种关联在 /vendor/etc/input-port-associations.xml
中配置。例如:
<ports> <port display="0" input="usb-xhci-hcd.0.auto-1.1/input0" /> <port display="1" input="usb-xhci-hcd.0.auto-1.2/input0" /> </ports>
在上面的示例中,display="0"
指定与屏幕连接的端口。input="usb-xhci-hcd.0.auto-1.1/input0"
指定与输入设备连接的端口。如需确定与特定设备关联的端口,请使用 dumpsys input 命令 ,然后在 Event Hub 状态中查看这些设备的 location
属性。或者使用 dumpsys SurfaceFlinger --display-id
转储所有已连接屏幕的标识信息。
详细参考:
https://source.android.google.cn/docs/core/display/multi_display/displays?hl=zh-cn
https://source.android.google.cn/docs/core/display/multi_display/input-routing?hl=zh-cn
posted on 2023-08-14 16:20 sheldon_blogs 阅读(3453) 评论(0) 编辑 收藏 举报