Skip to content

Commit 1cf70bb

Browse files
committed
Screen magnification - feature - framework.
This change is the initial check in of the screen magnification feature. This feature enables magnification of the screen via global gestures (assuming it has been enabled from settings) to allow a low vision user to efficiently use an Android device. Interaction model: 1. Triple tap toggles permanent screen magnification which is magnifying the area around the location of the triple tap. One can think of the location of the triple tap as the center of the magnified viewport. For example, a triple tap when not magnified would magnify the screen and leave it in a magnified state. A triple tapping when magnified would clear magnification and leave the screen in a not magnified state. 2. Triple tap and hold would magnify the screen if not magnified and enable viewport dragging mode until the finger goes up. One can think of this mode as a way to move the magnified viewport since the area around the moving finger will be magnified to fit the screen. For example, if the screen was not magnified and the user triple taps and holds the screen would magnify and the viewport will follow the user's finger. When the finger goes up the screen will clear zoom out. If the same user interaction is performed when the screen is magnified, the viewport movement will be the same but when the finger goes up the screen will stay magnified. In other words, the initial magnified state is sticky. 3. Pinching with any number of additional fingers when viewport dragging is enabled, i.e. the user triple tapped and holds, would adjust the magnification scale which will become the current default magnification scale. The next time the user magnifies the same magnification scale would be used. 4. When in a permanent magnified state the user can use two or more fingers to pan the viewport. Note that in this mode the content is panned as opposed to the viewport dragging mode in which the viewport is moved. 5. When in a permanent magnified state the user can use three or more fingers to change the magnification scale which will become the current default magnification scale. The next time the user magnifies the same magnification scale would be used. 6. The magnification scale will be persisted in settings and in the cloud. Note: Since two fingers are used to pan the content in a permanently magnified state no other two finger gestures in touch exploration or applications will work unless the uses zooms out to normal state where all gestures works as expected. This is an intentional tradeoff to allow efficient panning since in a permanently magnified state this would be the dominant action to be performed. Design: 1. The window manager exposes APIs for setting accessibility transformation which is a scale and offsets for X and Y axis. The window manager queries the window policy for which windows will not be magnified. For example, the IME windows and the navigation bar are not magnified including windows that are attached to them. 2. The accessibility features such a screen magnification and touch exploration are now impemented as a sequence of transformations on the event stream. The accessibility manager service may request each of these features or both. The behavior of the features is not changed based on the fact that another one is enabled. 3. The screen magnifier keeps a viewport of the content that is magnified which is surrounded by a glow in a magnified state. Interactions outside of the viewport are delegated directly to the application without interpretation. For example, a triple tap on the letter 'a' of the IME would type three letters instead of toggling magnified state. The viewport is updated on screen rotation and on window transitions. For example, when the IME pops up the viewport shrinks. 4. The glow around the viewport is implemented as a special type of window that does not take input focus, cannot be touched, is laid out in the screen coordiates with width and height matching these of the screen. When the magnified region changes the root view of the window draws the hightlight but the size of the window does not change - unless a rotation happens. All changes in the viewport size or showing or hiding it are animated. 5. The viewport is encapsulated in a class that knows how to show, hide, and resize the viewport - potentially animating that. This class uses the new animation framework for animations. 6. The magnification is handled by a magnification controller that keeps track of the current trnasformation to be applied to the screen content and the desired such. If these two are not the same it is responsibility of the magnification controller to reconcile them by potentially animating the transition from one to the other. 7. A dipslay content observer wathces for winodw transitions, screen rotations, and when a rectange on the screen has been reqeusted. This class is responsible for handling interesting state changes such as changing the viewport bounds on IME pop up or screen rotation, panning the content to make a requested rectangle visible on the screen, etc. 8. To implement viewport updates the window manger was updated with APIs to watch for window transitions and when a rectangle has been requested on the screen. These APIs are protected by a signature level permission. Also a parcelable and poolable window info class has been added with APIs for getting the window info given the window token. This enables getting some useful information about a window. There APIs are also signature protected. bug:6795382 Change-Id: Iec93da8bf6376beebbd4f5167ab7723dc7d9bd00
1 parent fa8d83d commit 1cf70bb

34 files changed

+3086
-260
lines changed

Android.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ LOCAL_SRC_FILES += \
153153
core/java/android/view/accessibility/IAccessibilityManager.aidl \
154154
core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
155155
core/java/android/view/IApplicationToken.aidl \
156+
core/java/android/view/IDisplayContentChangeListener.aidl \
156157
core/java/android/view/IInputFilter.aidl \
157158
core/java/android/view/IInputFilterHost.aidl \
158159
core/java/android/view/IOnKeyguardExitResult.aidl \

api/current.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16418,6 +16418,7 @@ package android.os {
1641816418
method public void finishBroadcast();
1641916419
method public java.lang.Object getBroadcastCookie(int);
1642016420
method public E getBroadcastItem(int);
16421+
method public int getRegisteredCallbackCount();
1642116422
method public void kill();
1642216423
method public void onCallbackDied(E);
1642316424
method public void onCallbackDied(E, java.lang.Object);

core/java/android/os/RemoteCallbackList.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,4 +304,25 @@ public void finishBroadcast() {
304304

305305
mBroadcastCount = -1;
306306
}
307+
308+
/**
309+
* Returns the number of registered callbacks. Note that the number of registered
310+
* callbacks may differ from the value returned by {@link #beginBroadcast()} since
311+
* the former returns the number of callbacks registered at the time of the call
312+
* and the second the number of callback to which the broadcast will be delivered.
313+
* <p>
314+
* This function is useful to decide whether to schedule a broadcast if this
315+
* requires doing some work which otherwise would not be performed.
316+
* </p>
317+
*
318+
* @return The size.
319+
*/
320+
public int getRegisteredCallbackCount() {
321+
synchronized (mCallbacks) {
322+
if (mKilled) {
323+
return 0;
324+
}
325+
return mCallbacks.size();
326+
}
327+
}
307328
}

core/java/android/provider/Settings.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3399,6 +3399,46 @@ public static final String getBluetoothInputDevicePriorityKey(String address) {
33993399
public static final String ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS =
34003400
"accessibility_web_content_key_bindings";
34013401

3402+
/**
3403+
* Setting that specifies whether the display magnification is enabled.
3404+
* Display magnifications allows the user to zoom in the display content
3405+
* and is targeted to low vision users. The current magnification scale
3406+
* is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}.
3407+
*
3408+
* @hide
3409+
*/
3410+
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
3411+
"accessibility_display_magnification_enabled";
3412+
3413+
/**
3414+
* Setting that specifies what the display magnification scale is.
3415+
* Display magnifications allows the user to zoom in the display
3416+
* content and is targeted to low vision users. Whether a display
3417+
* magnification is performed is controlled by
3418+
* {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
3419+
*
3420+
* @hide
3421+
*/
3422+
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE =
3423+
"accessibility_display_magnification_scale";
3424+
3425+
/**
3426+
* Setting that specifies whether the display magnification should be
3427+
* automatically updated. If this fearture is enabled the system will
3428+
* exit magnification mode or pan the viewport when a context change
3429+
* occurs. For example, on staring a new activity or rotating the screen,
3430+
* the system may zoom out so the user can see the new context he is in.
3431+
* Another example is on showing a window that is not visible in the
3432+
* magnified viewport the system may pan the viewport to make the window
3433+
* the has popped up so the user knows that the context has changed.
3434+
* Whether a screen magnification is performed is controlled by
3435+
* {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
3436+
*
3437+
* @hide
3438+
*/
3439+
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE =
3440+
"accessibility_display_magnification_auto_update";
3441+
34023442
/**
34033443
* The timout for considering a press to be a long press in milliseconds.
34043444
* @hide
@@ -4806,6 +4846,9 @@ public static final String getBluetoothInputDevicePriorityKey(String address) {
48064846
PARENTAL_CONTROL_ENABLED,
48074847
PARENTAL_CONTROL_REDIRECT_URL,
48084848
USB_MASS_STORAGE_ENABLED,
4849+
ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
4850+
ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
4851+
ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
48094852
ACCESSIBILITY_SCRIPT_INJECTION,
48104853
BACKUP_AUTO_RESTORE,
48114854
ENABLED_ACCESSIBILITY_SERVICES,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
** Copyright 2012, The Android Open Source Project
3+
**
4+
** Licensed under the Apache License, Version 2.0 (the "License")
5+
** you may not use this file except in compliance with the License.
6+
** You may obtain a copy of the License at
7+
**
8+
** http://www.apache.org/licenses/LICENSE-2.0
9+
**
10+
** Unless required by applicable law or agreed to in writing, software
11+
** distributed under the License is distributed on an "AS IS" BASIS,
12+
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
** See the License for the specific language governing permissions and
14+
** limitations under the License.
15+
*/
16+
17+
package android.view;
18+
19+
import android.os.IBinder;
20+
import android.view.WindowInfo;
21+
import android.graphics.Rect;
22+
23+
/**
24+
* Interface for observing content changes on a display.
25+
*
26+
* {@hide}
27+
*/
28+
oneway interface IDisplayContentChangeListener {
29+
void onWindowTransition(int displayId, int transition, in WindowInfo info);
30+
void onRectangleOnScreenRequested(int displayId, in Rect rectangle, boolean immediate);
31+
void onRotationChanged(int rotation);
32+
}

core/java/android/view/IWindowManager.aidl

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import android.graphics.Point;
2626
import android.graphics.Rect;
2727
import android.os.IRemoteCallback;
2828
import android.view.IApplicationToken;
29+
import android.view.IDisplayContentChangeListener;
2930
import android.view.IOnKeyguardExitResult;
3031
import android.view.IRotationWatcher;
3132
import android.view.IWindowSession;
@@ -34,7 +35,8 @@ import android.view.InputEvent;
3435
import android.view.MotionEvent;
3536
import android.view.InputChannel;
3637
import android.view.InputDevice;
37-
import android.view.IInputFilter;
38+
import android.view.IInputFilter;
39+
import android.view.WindowInfo;
3840

3941
/**
4042
* System private interface to the window manager.
@@ -213,11 +215,6 @@ interface IWindowManager
213215
*/
214216
IBinder getFocusedWindowToken();
215217

216-
/**
217-
* Gets the frame on the screen of the window given its token.
218-
*/
219-
boolean getWindowFrame(IBinder token, out Rect outBounds);
220-
221218
/**
222219
* Gets the compatibility scale of e window given its token.
223220
*/
@@ -227,4 +224,29 @@ interface IWindowManager
227224
* Sets an input filter for manipulating the input event stream.
228225
*/
229226
void setInputFilter(in IInputFilter filter);
227+
228+
/**
229+
* Sets the scale and offset for implementing accessibility magnification.
230+
*/
231+
void magnifyDisplay(int dipslayId, float scale, float offsetX, float offsetY);
232+
233+
/**
234+
* Adds a listener for display content changes.
235+
*/
236+
void addDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
237+
238+
/**
239+
* Removes a listener for display content changes.
240+
*/
241+
void removeDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
242+
243+
/**
244+
* Gets the info for a window given its token.
245+
*/
246+
WindowInfo getWindowInfo(IBinder token);
247+
248+
/**
249+
* Gets the infos for all visible windows.
250+
*/
251+
void getVisibleWindowsForDisplay(int displayId, out List<WindowInfo> outInfos);
230252
}

core/java/android/view/IWindowSession.aidl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,9 @@ interface IWindowSession {
180180

181181
void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
182182
float dsdx, float dtdx, float dsdy, float dtdy);
183+
184+
/**
185+
* Notifies that a rectangle on the screen has been requested.
186+
*/
187+
void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate);
183188
}

core/java/android/view/View.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6259,6 +6259,11 @@ public boolean requestAccessibilityFocus() {
62596259
if (viewRootImpl != null) {
62606260
viewRootImpl.setAccessibilityFocus(this, null);
62616261
}
6262+
if (mAttachInfo != null) {
6263+
Rect rectangle = mAttachInfo.mTmpInvalRect;
6264+
getDrawingRect(rectangle);
6265+
requestRectangleOnScreen(rectangle);
6266+
}
62626267
invalidate();
62636268
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
62646269
notifyAccessibilityStateChanged();

core/java/android/view/ViewRootImpl.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4649,9 +4649,19 @@ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
46494649
// ViewAncestor never intercepts touch event, so this can be a no-op
46504650
}
46514651

4652-
public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
4653-
boolean immediate) {
4654-
return scrollToRectOrFocus(rectangle, immediate);
4652+
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
4653+
final boolean scrolled = scrollToRectOrFocus(rectangle, immediate);
4654+
if (rectangle != null) {
4655+
mTempRect.set(rectangle);
4656+
mTempRect.offset(0, -mCurScrollY);
4657+
mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
4658+
try {
4659+
mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect, immediate);
4660+
} catch (RemoteException re) {
4661+
/* ignore */
4662+
}
4663+
}
4664+
return scrolled;
46554665
}
46564666

46574667
public void childHasTransientStateChanged(View child, boolean hasTransientState) {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
**
3+
** Copyright 2012, The Android Open Source Project
4+
**
5+
** Licensed under the Apache License, Version 2.0 (the "License")
6+
** you may not use this file except in compliance with the License.
7+
** You may obtain a copy of the License at
8+
**
9+
** http://www.apache.org/licenses/LICENSE-2.0
10+
**
11+
** Unless required by applicable law or agreed to in writing, software
12+
** distributed under the License is distributed on an "AS IS" BASIS,
13+
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
** See the License for the specific language governing permissions and
15+
** limitations under the License.
16+
*/
17+
18+
package android.view;
19+
20+
parcelable WindowInfo;

0 commit comments

Comments
 (0)