目录
1.1.2.1.从AndroidManifest.xml开始 6
3.2.2.updateRotationPreferenceDescription方法的构建 22
前言
本文档是在全志A33平台 Android 系统中,关于屏幕旋转方面的技术调研。
李瀚
2015年4月14日
1.关于
决定屏幕方向有两个层面,一个是 Linux 内核层的 FB 方向(这里暂不深入),驱动上做的修改,另一个是 Android OS 上的修改。
在 Linux 内核初始化以后,init进程启动,紧接着启动了Zygote。Zygote让Dalvik虚拟机共享代码、低内存占用以及最小的启动时间成为可能。Zygote是一个虚拟器进程,正如我们在前一个步骤所说的在系统引 导的时候启动。Zygote预加载以及初始化核心库类。通常,这些核心类一般是只读的,也是Android SDK或者核心框架的一部分。在Java虚拟机中,每一个实例都有它自己的核心库类文件和堆对象的拷贝。
如上图,Zygote启动以后,初始化了 System.Settings,开始了SystemUI 和 Settings 的进程、监听屏幕旋转机制和传感器管理器。当来自 SystemUI 或者 Settings 对 System.Settings 数据库的设置生效,监听屏幕旋转机制就打开,开始读取来由自底层设备驱动经过HAL经过JNI到传感器管理器的数据,对屏幕进行旋转设置。
1.1.Framework 层屏幕旋转原理
最终实现状态栏的快速设置功能是在 SystemUI 上。所以先从 SystemUI 入手。
1.1.1.初识SystemUI
Android 4.4,是由 Google 公司制作和研发的代号为 KitKat 的手机操作系统,于北京时间 2013 年 9 月 4 日凌晨对外公布了该 Android 新版本的名称,为 Android 4.4 (代号 KitKat 奇巧)。
Android 4.4 同时适用于Phone和Tablet(TV),因此,对于 Phone 来说 SystemUI 指的是:StatusBar(状态栏)、 NavigationBar(导航栏)。而对于 Tablet 或者是 TV 来说 SystemUI 指的是:CombinedBar(包括了 StatusBar 和 NavigationBar)。
Android 的 Phone 的信号,蓝牙标志,Wifi 标志等等这些状态显示标志都会在 StatusBar 上显示。当我们的设备开机后,首先需要给用户呈现的就是各种界面同时也包括了我们的 SystemUI,因此对于整个 Android 系统来说,SystemUI 都有举足轻重的作用。
1.1.2.分析 SystemUI 代码
1.1.2.1.从AndroidManifest.xml开始
在 Android 4.4 中,Google 整合了 Phone 和 Tablet(TV) 的 SystemUI,也就说可以根据设备的类型自动匹配相应的 SystemUI。首先从 AndroidManifest.xml 入手。
(省略)
<!-- Wifi Display -->
<uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
<application
android:persistent="true"
android:allowClearUserData="false"
android:allowBackup="false"
android:hardwareAccelerated="true"
android:label="@string/app_label"
android:icon="@*android:drawable/platlogo"
android:process="com.android.systemui"
android:supportsRtl="true">
<!-- Broadcast receiver that gets the broadcast at boot time and starts
up everything else.
TODO: Should have an android:permission attribute
-->
<service android:name="SystemUIService"
android:exported="true"
/>
<!-- started from PhoneWindowManager
TODO: Should have an android:permission attribute -->
<service android:name=".screenshot.TakeScreenshotService"
android:process=":screenshot"
android:exported="false" />
<service android:name=".LoadAverageService"
android:exported="true" />
<service android:name=".ImageWallpaper"
android:permission="android.permission.BIND_WALLPAPER"
android:exported="true" />
(省略)
根据以上代码我们可以发现这其中注册了很多 Service,同时也包括了广播。但这里我们只关注 SystemUIService。首先要找到 SystemUIService 是如何启动的。Service 的启动,不外乎 startService(intent) 和 bindService(intent),它们都是以 intent 为对象,由于 intent 的声明也需要 SystemUIService,因此我们可以据此搜索关键词"SystemUIService"。
1.1.2.2.SystemUIService分析
经过搜索发现:
frameworks/base/services/java/com/android/server/SystemServer.java
static final void startSystemUi(Context context) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.OWNER);
}
这部分是开始了 SystemUI,可以确认下面代码是上面服务的具体实现:
frameworks/base/packages/systemui/src/com/android/systemui/SystemUIService.java
public class SystemUIService extends Service {
private static final String TAG = "SystemUIService";
/**
* The classes of the stuff to start.
*/
private final Class<?>[] SERVICES = new Class[] {
com.android.systemui.recent.Recents.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.settings.SettingsUI.class,
};
/**
* Hold a reference on the stuff we start.
*/
private final SystemUI[] mServices = new SystemUI[SERVICES.length];
@Override
public void onCreate() {
HashMap<Class<?>, Object> components =
new HashMap<Class<?>, Object>();
final int N = SERVICES.length;
for (int i=0; i<N; i++) {
Class<?> cl = SERVICES[i];
Log.d(TAG, "loading: " + cl);
try {
mServices[i] = (SystemUI)cl.newInstance();
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
}
mServices[i].mContext = this;
mServices[i].mComponents = components;
Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
}
}
(省略)
这里初始化了以下类
Recent(最近打开程序)
com.android.systemui.recent.Recents.class
Statusbar(状态栏)
com.android.systemui.statusbar.SystemBars.class
USBStorageNotification(USB存储设备通知) com.android.systemui.usb.StorageNotification.class
PowerUI
com.android.systemui.power.PowerUI.class
RingtonePlayer
com.android.systemui.media.RingtonePlayer.class
SettingsUI(设置界面)
com.android.systemui.settings.SettingsUI.class
4.0以前版本,这里都是整合在一起的,4.4这里开始分离出来,分一个个模块。以后若是针对某个模块分析,在这里开始了分支。
1.1.2.3.Statusbar分析
接下来重点是 Statusbar(状态栏),这个类构建在:
/frameworks/base/packages/systemui/src/com/android/systemui/statusbar/SystemBars.java 分析得出接下来调用了frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
// 创建一个快速设置菜单QuickSettings的实例
QuickSettings mQS;
mQS = new QuickSettings(mContext, mSettingsContainer);
类原型来自:
frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettings.java
这里出现了关键代码,添加删除下拉快速设置可以模仿如下内容添加:
// 声明下文所需的RotationLockController类
private RotationLockController mRotationLockController;
// 声明下文所需的QuickSettingsModel 类
private QuickSettingsModel mModel;
// R.bool.quick_settings_show_rotation_lock 作为一个配置设置项
// 位于frameworks/base/packages/systemui/res/value/Config.xml提供了原始的设置
// 由于自适应机制,这里真正调用的是
// 位于frameworks/base/packages/systemui/res/values-sw600dp/Config.xml
// 类似的按钮也可以在这里设置是否需要显示
// DEBUG_GONE_TILES设置调试时屏蔽选项
// 这里作为旋转屏幕部分的开始,用了两个配置项做约束条件
if (mContext.getResources().getBoolean(R.bool.quick_settings_show_rotation_lock)|| DEBUG_GONE_TILES) {
// QuickSettingsBasicTile 提供了监听按钮是否按下的方法。
// 原型位于:
// frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsBasicTile.java
// 新建快速设置的按钮,这里新建了一个按钮的对象
final QuickSettingsBasicTile rotationLockTile
= new QuickSettingsBasicTile(mContext);
// 设置按钮的Listenter,用于监听按钮是否被按下
rotationLockTile.setOnClickListener(new View.OnClickListener() {
// 这里开始创建按钮按下的方法
public void onClick(View view) {
// 声明了一个locked变量用来表示屏幕当前的锁定状态
// 取值自isRotationLocked 方法,该方法提供获取屏幕的锁定状态
// 详见1.1.2.3.1.分析 RotationLockController.isRotationLocked 方法
final boolean locked =
mRotationLockController.isRotationLocked();
// 调用了RotationLockController.setRotationLocked方法
// 该方法提供提供了设置了设置屏幕旋转锁定与否的功能
// 如果传入的参数,即locked的值
// 为true,则锁定屏幕旋转
// 为false,则解锁屏幕旋转
// 这里locked取反,使得每次按下按钮时,锁定、解锁状态相互切换
// 详见 1.1.2.3.2.分析RotationLockController.setRotationLocked方法
mRotationLockController.setRotationLocked(!locked);
}
});
// addRotationLockTile是由QuickSettingsModel提供的方法
// 用来设置旋转按钮上面标签、图标的变化。
// rotationLockTile 、mRotationLockController 两个类的上下文传入该方法
// 并创建了刷新的回调函数
// 详见 1.1.2.3.3.分析 QuickSettingsModel.addRotationLockTile方法
mModel.addRotationLockTile(rotationLockTile, mRotationLockController,
new QuickSettingsModel.RefreshCallback() {
// QuickSettingsTileView 提供了设置按钮各种显示相关的方法和参数
// State 在QuickSettingsModel中实现
// static class State {
// int iconId;
// String label;
// boolean enabled = false;
// }
// 作为父类,其生成了很多用于各种设置的子类
// 其中有QuickSettingsModel.RotationLockState
// 实现刷新界面的方法,当按钮发生改变的时候
// 这里被调用到用于刷新按钮上的图标和标签
public void refreshView(QuickSettingsTileView view, State state) {
// 声明rotationLockState 用于存储当前屏幕锁定状态
// 从传参得到的state
// 强制转换成QuickSettingsModel.RotationLockState
// 并将其赋值给rotationLockState
QuickSettingsModel.RotationLockState rotationLockState =
(QuickSettingsModel.RotationLockState) state;
// Visibility的三个属性
// VISIBLE:设置控件可见
// INVISIBLE:设置控件不可见
// GONE:设置控件隐藏
// 而INVISIBLE和GONE的主要区别是:
// 当控件visibility属性为INVISIBLE时,
// 界面保留了view控件所占有的空间;
// 而控件属性为GONE时,界面则不保留view控件所占有的空间。
// 这里设置当rotationLockState.visible
// 为true,往view.setVisibility传入View.VISIBLE
// 为false,则传入View.GONE
view.setVisibility(rotationLockState.visible ? View.VISIBLE : View.GONE);
// 检测图标状态设置是否存在,是的话
if (state.iconId != 0) {
// 首先清空已有的图标
rotationLockTile.setImageDrawable(null);
// 设置图标
rotationLockTile.setImageResource(state.iconId); }
// 检测标签状态设置是否存在,是的话
if (state.label != null) {
// 设置标签
rotationLockTile.setText(state.label);
}
}
});
// 传入了按钮的句柄,把该按钮加入并显示出来
// 详见 1.1.2.3.4.分析addView方法
parent.addView(rotationLockTile);
}
1.1.2.3.1.分析 RotationLockController.isRotationLocked 方法
用于判断屏幕旋转是否处于锁定状态
位于:
frameworks/base/packages/systemui/src/com/android/systemui/statusbar/policy/RotationLockController.java
源码:
public boolean isRotationLocked() {
// 如果旋转开关被支持则调用RotationPolicy.isRotationLocked
// 用来返回屏幕旋转锁定状态
// 详见 1.1.2.3.1.1.分析 RotationPolicy.isRotationLocked 方法
if (RotationPolicy.isRotationLockToggleSupported(mContext)) {
return RotationPolicy.isRotationLocked(mContext);
}
return false;
}
1.1.2.3.1.1.分析 RotationPolicy.isRotationLocked 方法
用于判断屏幕旋转是否处于锁定状态
位于:
frameworks/base/core/java/com/android/internal/view/RotationPolicy.java
源码:
public static boolean isRotationLocked(Context context) {
return Settings.System.getIntForUser(
context.getContentResolver(),
Settings.System.ACCELEROMETER_ROTATION,
0,
UserHandle.USER_CURRENT) == 0;
}
// 这里调用了返回系统API Settings.System.getIntForUser读取系统设置
// 读取了ACCELEROMETER_ROTATION的值
// ACCELEROMETER_ROTATION 是 Settings.System 的一个键值,
// 决定了是否使用加速度计控制屏幕旋转角度。
// 详见 1.1.3.关于Settings.System
1.1.2.3.2.分析RotationLockController.setRotationLocked方法
用于设置屏幕旋转是否处于锁定状态
位于:
frameworks/base/packages/systemui/src/com/android/systemui/statusbar/policy/RotationLockController.java
源码:
public void setRotationLocked(boolean locked) {
// 如果旋转开关被支持则返回RotationPolicy.isRotationLocked
// 用于判断屏幕选装是否锁定
// 详见 1.1.2.3.2.1.分析RotationLockController.setRotationLocked方法
if (RotationPolicy.isRotationLockToggleSupported(mContext)) {
RotationPolicy.setRotationLock(mContext, locked);
}
}
1.1.2.3.2.1.分析RotationLockController.setRotationLocked方法
用于设置屏幕锁定与否
位于:
frameworks/base/core/java/com/android/internal/view/RotationPolicy.java
源码:
public static void setRotationLock(Context context, final boolean enabled) {
// 向 Settings.System. 写入HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY
// 如果0,那么不隐藏旋转锁切换的可访问性(尽管它可能因其他原因不可用)。
// 如果1,那么旋转锁切换隐藏。
Settings.System.putIntForUser(
context.getContentResolver(),
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
0,
UserHandle.USER_CURRENT);
// AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,
// 在类中实现异步操作,并提供接口反馈当前异步执行的程度
// (可以通过接口实现UI进度更新),
// 最后反馈执行的结果给UI主线程.
AsyncTask.execute(new Runnable() {
public void run() {
try {
//获取系统WindowManager服务,该类提供了关于窗口管理方面的方法
IWindowManager wm =
WindowManagerGlobal.getWindowManagerService();
// 传参为 true,
// 则 wm.freezeRotation(-1) 锁定屏幕,并设置 -1 表示默认值
// 传参为 false,
// 则 wm.thawRotation() 设定有加速度计决定屏幕方向
if (enabled) {
// freezeRotation 详见 1.1.2.3.2.1.分析freezeRotation
wm.freezeRotation(-1);
} else {
// thawRotation详见 1.1.2.3.2.2.分析thawRotation
wm.thawRotation();
}
} catch (RemoteException exc) {
Log.w(TAG, "Unable to save auto-rotate setting");
}
}
});
}
1.1.2.3.2.1.1.分析freezeRotation
用于锁定屏幕方向
位于:frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
源码:
public void freezeRotation(int rotation) {
// 第一步,检查相对应的权限。
if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
"freezeRotation()")) {
throw new SecurityException("Requires SET_ORIENTATION permission");
}
//第二步,如果传入的rotation值小于-1或者大于270度则抛出异常
if (rotation < -1 || rotation > Surface.ROTATION_270) {
throw new IllegalArgumentException("Rotation argument must be -1 or a v alid "+ "rotation constant.");
}
if (DEBUG_ORIENTATION)
Slog.v(TAG, "freezeRotation: mRotation=" + mRotation);
long origId = Binder.clearCallingIdentity();
try {
// PhoneWindowManager.setUserRotationMode
// 用于设置屏幕旋转锁定模式
// 详见 1.1.2.3.2.1.1.进一步分析PhoneWindowManager.setUserRotationMode
mPolicy.setUserRotationMode(
WindowManagerPolicy.USER_ROTATION_LOCKED,
rotation == -1 ? mRotation : rotation);
} finally {
Binder.restoreCallingIdentity(origId);
}
// 更新旋转状态
updateRotationUnchecked(false, false);
}
1.1.2.3.2.1.1.1.进一步分析PhoneWindowManager.setUserRotationMode
用于设置旋转模式
位于:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
源码:
public void setUserRotationMode(int mode, int rot) {
ContentResolver res = mContext.getContentResolver();
// 如果传参等于 USER_ROTATION_LOCKED
if (mode == WindowManagerPolicy.USER_ROTATION_LOCKED) {
// 通过 Settings.System 设置 Settings.System.USER_ROTATION
// 为传参 rot 的值,即设置屏幕旋转角度
Settings.System.putIntForUser(res,
Settings.System.USER_ROTATION,
rot,
UserHandle.USER_CURRENT);
// 通过 Settings.System 设置 Settings.System.ACCELEROMETER_ROTATION
// 为0,即设置不根据加速度计改变屏幕方向
Settings.System.putIntForUser(res,
Settings.System.ACCELEROMETER_ROTATION,
0,
UserHandle.USER_CURRENT);
} else {
// 通过 Settings.System 设置 Settings.System.ACCELEROMETER_ROTATION
// 为1,即设置根据加速度计改变屏幕方向
Settings.System.putIntForUser(res,
Settings.System.ACCELEROMETER_ROTATION,
1,
UserHandle.USER_CURRENT);
}
}
用于设置屏幕旋转由加速度计决定
位于:frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
源码:
public void thawRotation() {
// 第一步,检查权限。
if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
"thawRotation()")) {
throw new SecurityException("Requires SET_ORIENTATION permission");
}
if (DEBUG_ORIENTATION)
Slog.v(TAG, "thawRotation: mRotation=" + mRotation);
long origId = Binder.clearCallingIdentity();
try {
// PhoneWindowManager.setUserRotationMode 用于设置屏幕旋转锁定模式
// 详见 1.1.2.3.2.1.1.进一步分析PhoneWindowManager.setUserRotationMode
mPolicy.setUserRotationMode(
WindowManagerPolicy.USER_ROTATION_FREE,
777); // rot not used
} finally {
Binder.restoreCallingIdentity(origId);
}
// 更新旋转状态
updateRotationUnchecked(false, false);
}
1.1.2.3.3.分析 QuickSettingsModel.addRotationLockTile方法
调用了QuickSettingsModel.addRotationLockTile 值,用于设置快速设置的图标、标签,提供其刷新的方法
位于:
frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
源码:
void addRotationLockTile(QuickSettingsTileView view,
RotationLockController rotationLockController,
RefreshCallback cb) {
// 传入设置菜单的按钮
mRotationLockTile = view;
// 传入回调
mRotationLockCallback = cb;
// 传入旋转控制类
mRotationLockController = rotationLockController;
// 使旋转状态改变图标标签等状态
// 详见 1.1.2.3.2.1.分析QuickSettingsModel.onRotationLockChanged 方法
onRotationLockChanged();
}
1.1.2.3.1.分析QuickSettingsModel.onRotationLockChanged 方法
用于调用 QuickSettingsModel.onRotationLockChanged ,使旋转状态改变图标标签等状态
位于:
frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
源码:
void onRotationLockChanged() {
// 调用 QuickSettingsModel.onRotationLockStateChanged
// 实现旋转状态改变图标标签等状态
// 传入判断是否锁屏状态方法及判断按钮是否可见方法
// 详见 1.1.2.3.2.1.1.分析QuickSettingsModel.onRotationLockStateChanged方法
onRotationLockStateChanged(
mRotationLockController.isRotationLocked(),
mRotationLockController.isRotationLockAffordanceVisible());
}
1.1.2.3.1.1.分析QuickSettingsModel.onRotationLockStateChanged方法
实现旋转状态改变图标标签等状态
位于:
frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
源码:
public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
// 传入锁屏状态是否可见
mRotationLockState.visible = affordanceVisible;
// 传入屏幕旋转是否是锁屏状态
mRotationLockState.enabled = rotationLocked;
// 判断屏幕状态,
// 为true,锁屏,则mRotationLockState.iconId赋值指向锁屏图标的资源
// 为false,锁屏,则mRotationLockState.iconId赋值指向旋转图标的资源
mRotationLockState.iconId = rotationLocked
? R.drawable.ic_qs_rotation_locked
: R.drawable.ic_qs_auto_rotate;
// 判断屏幕状态,
// 为true,锁屏,则mRotationLockState.iconId赋值指向锁屏标签的资源
// 为false,锁屏,则mRotationLockState.iconId赋值指向旋转标签的资源
mRotationLockState.label = rotationLocked
? mContext.getString(R.string.quick_settings_rotation_locked_label)
: mContext.getString(R.string.quick_settings_rotation_unlocked_label);
// 刷新状态
mRotationLockCallback.refreshView(mRotationLockTile, mRotationLockState);
}
1.1.2.3.4.分析addView方法
// 添加子视图。如果没有设置布局参数,那么使用默认值。
// 传入时,我们只传入一个child参数,这时候再加入index参数
public void addView(View child) {
addView(child, -1);
}
// 加入index参数
public void addView(View child, int index) {
// 尝试用getLayoutParams获取params参数
LayoutParams params = child.getLayoutParams();
// 如果获取不到params
if (params == null) {
// 尝试使用generateDefaultLayoutParams获得
params = generateDefaultLayoutParams();
// 如果获取不到则抛出异常
if (params == null) {
throw new IllegalArgumentException(
"generateDefaultLayoutParams() cannot return null");
}
}
// 传入params到有三个参数的addView
addView(child, index, params);
}
// 添加一个与指定的子视图布局参数
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
// addViewInner() 将会调用 child.requestLayout() 当设置一个布局参数
// 所以调用之前调用 requestLayout() ,
// 因此child的请求会被要求在这一层
requestLayout();
// 调用该方法可以是绘图缓存失效
invalidate(true);
// 实现显示绘图部分
addViewInner(child, index, params, false);
}
1.1.3.关于Settings.System
Settings.System.提供类似Settings.System.putIntForUser、getIntForUser等系统设置的API,它的作用可以对系统的全局设置数据库进行操作。具体可以参考官方API文档,或者在阅读源码:
frameworks/base/core/java/android/provider/Settings.java
指令 USER_ROTATION 作用:
当没有其他设置有效时设置默认屏幕旋转方向。
当 ACCELEROMETER_ROTATION 为零,必须设置这个值。
指令 ACCELEROMETER_ROTATION 作用:
控制加速度计是否会被用来改变屏幕取向。如果为0,它将不能使用,除非显式地请求由应用程序;如果1,那么它将使用默认情况下,除非显式由应用程序禁用。
Settings.System 提供了一套键值对的形式,将一些特定的值以全局的模式保存到 Setting 的数据库中。我们可以通过它提供的 get 或者 put 的方法对其中的数据进行读写。我们也可以在 frameworks 中添加自己所需要的一些特定值。
在应用程序层面,如需操作在 AndroidMainfes.xml 添加权限:
<uses-permission android:name="android.permission.READ_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
Settings.System 由 frameworks/base/core/java/android/provider/Setting.java实现。
1.2.旋转监听机制分析
Zygote 启动了 WindowManager 以后,调用了 PhoneManager的 rotationForOrientationLw,其实际调用了 WindowOrientationListener,WindowOrientationListener 中监听了 Sensor 的值,对旋转方向进行了判断,实现、对四个不同方向做出响应 。
OrientationListener提供了一个抽象的旋转监听机制。
OrientationEventListener提供了具体的实现。
1.3.加速度传感器架构分析
Android 4.4 系统支持了20种传感器,其中加速度传感器提供了手机旋转的矢量。
Android 传感器系统由以下几部分构成:
类别 | 名称 | 代码 |
用户空间 | Java应用程序 | 用户实现 |
Framework框架层 | SensorManager.java SensorListener.java SensorEvent.Java ... | |
JNI层 | android_hardware_SensorManager.cpp com_android_server_SensorService.java | |
HAL硬件抽象层 | 用户实现 | |
内核空间 | 设备驱动程序 | 用户实现 |
具体硬件 | 比如加速度传感器 |
|
各部分之间架构图如下:
1.3.1.内核驱动层
一般加速度模块使用I2C对硬件进行操作,生成了设备节点,对上层提供例如open(), read(), write(), ioctl(), poll()等函数调用的方式。
1.3.2.HAL硬件抽象层
与底层设备驱动程序进行交互。
用于交互的关键是文件描述符fd,fd通过open()打开加速度传感器的设备节点而得到,
即 fd = open ("/dev/bma220", O_RDONLY);
这里的/dev/bma220这个设备节点是举个例子,因为目前设备上没有对应的硬件。
其他的函数调用如read(), write()等都通过该文件描述符fd对加速度传感器进行操作。
1.3.3.JNI层
JNI提供了从C++语言到JAVA语言的转换,它为Java Framework层提供一系列接口,而这些接口函数的具体实现中,利用例如module->methods->open(),sSensorDevice->data_open(), sSensorDevice->poll()等回调函数与硬件抽象层进行交互。而这些open(), poll()回调函数在硬件抽象层中具体实现。
1.3.4.Java Framework
Framework层提供各种类和类的对象,可作为系统的守护进程运行,也可供上层应用程序的使用。
例如类SensorManager,它作为系统的守护进程在初始化的时候开始运行,其子类SensorThread中的子类 SensorThreadRunnable通过sensors_data_poll()实现了对G-sensor数据的轮询访问,而 sensors_data_poll()通过JNI层转换到硬件抽象层去具体实现poll()。
2.修改快速设置中旋转屏幕按钮
2.1.需求:
把快速设置栏中的“屏幕锁定/自由旋转”按钮改成“旋转屏幕”按钮,实现每按一次屏幕旋转递增90°。
2.2.功能上的修改:
frameworks/base/core/java/com/android/internal/view/RotationPolicy.java
在 RotationPolicy 类里添加:
public static int rota = 0;
在 setRotationLock 方法里面,删除调用重力自由旋转的可能性:
//if (enabled) {
// wm.freezeRotation(-1);
//} else {
// wm.thawRotation();
//}
// 注释掉以上部分
final int cur = wm.getRotation();//获取当前屏幕旋转防线的值,4个方向用0~3表示
rota = cur;
rota++;
if(rota > 3)
rota = 0;
wm.freezeRotation(rota);//每次被调用锁定旋转的时候设置新的锁定方向,即原来方向的下一个方向
2.3.关于图标:
frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
做如下修改:
public void onRotationLockStateChanged(boolean rotationLocked,
boolean affordanceVisible) {
mRotationLockState.visible = affordanceVisible;
mRotationLockState.enabled = rotationLocked;
// mRotationLockState.iconId = rotationLocked
// ? R.drawable.ic_qs_rotation_locked
// : R.drawable.ic_qs_auto_rotate;
// 注释掉以上部分
// 强制的设置图标固定显示“旋转屏幕图标”
mRotationLockState.iconId = R.drawable.ic_qs_auto_rotate;
//mRotationLockState.label = rotationLocked
// ? mContext.getString(R.string.quick_settings_rotation_locked_label)
// : mContext.getString(R.string.quick_settings_rotation_unlocked_label);
// 注释掉以上部分
// 强制的设置图标固定显示“旋转屏幕标签”
mRotationLockState.label = mContext.getString(R.string.quick_settings_rotation_sreen_label);
mRotationLockCallback.refreshView(mRotationLockTile, mRotationLockState);
}
2.4.关于图标标签:
frameworks/base/packages/SystemUI/res/values/strings.xml
添加:
<string name="quick_settings_rotation_sreen_label">Rotate Sreen</string>
图标标签的中文翻译:
android/frameworks/base/packages/SystemUI/res/values-zh-rCN/strings.xml
添加:
<string name="quick_settings_rotation_sreen_label">"旋转屏幕"</string>
3.系统设置添加选项
思路是模仿休眠菜单进行添加
3.1.研究休眠菜单原理
休眠部分读取设置菜单的主要原理:
packages/apps/Settings/src/com/android/settings/DisplaySettings.java
// 把实际数值更新到界面上
private void updateTimeoutPreferenceDescription(long currentTimeout) {
ListPreference preference = mScreenTimeoutPreference;
// 声明一个字符串变量summary用于存储临时字符串
String summary;
// 若超时时间小于0,这种情况则置空临时字符串
if (currentTimeout < 0) {
summary = "";
} else {
// 声明变量entries ,存储读取自preference.getEntries,表示读取到的记录的内容
final CharSequence[] entries = preference.getEntries();
// 声明变量values ,存储读取自preference.getEntryValues,表示读取到的记录所在的值
final CharSequence[] values = preference.getEntryValues();
// 如果读取到的记录和记录长度为0,则置空字符串
if (entries == null || entries.length == 0) {
summary = "";
} else {
int best = 0;
for (int i = 0; i < values.length; i++) {
long timeout = Long.parseLong(values[i].toString());
if (currentTimeout >= timeout) {
best = i;
}
}
// 最后一项永不休眠
// 如果到了最后一项
if (best == values.length - 1)
// 设置字符串为“永远休眠”字样
summary = preference.getContext().getString(R.string.never_sleep);
else
// 设置字符串为XX分钟休眠
summary =
preference.getContext().getString(
R.string.screen_timeout_summary,
entries[best]);
}
}
// 把设置好的字符串显示到菜单项上
preference.setSummary(summary);
}
public boolean onPreferenceChange(Preference preference, Object objValue) {
final String key = preference.getKey();
if (KEY_SCREEN_TIMEOUT.equals(key)) {
int value = Integer.parseInt((String) objValue);
try {
// 调用Settings.System.putInt
// 设置SCREEN_OFF_TIMEOUT,用于设置屏幕关闭时间的
Settings.System.putInt(
getContentResolver(),
SCREEN_OFF_TIMEOUT,
value);
// 传入最新数值,更新最新的数值到界面上
updateTimeoutPreferenceDescription(value);
} catch (NumberFormatException e) {
Log.e(TAG, "could not persist screen timeout setting", e);
}
}
}
(省略...)
3.2.模仿休眠菜单原理构建
3.2.1.调用读取键值部分
实现在设置菜单中设置屏幕旋转角度的核心方法:
使用Settings.System.putInt,对USER_ROTATION的值进行设置。
packages/apps/Settings/src/com/android/settings/DisplaySettings.java
if (KEY_SCREEN_ROTATION.equals(key)) {
// 读取设置项上的值
int value = Integer.parseInt((String) objValue);
try {
// 通过 Settings.System.putInt 写入系统的数据库
Settings.System.putInt(getContentResolver(), USER_ROTATION, value);
// 把系统最新值显示到菜单上
updateRotationPreferenceDescription(value);
} catch (NumberFormatException e) {
Log.e(TAG, "could not ...ask Jangel", e);
}
}
3.2.2.updateRotationPreferenceDescription方法的构建
起着把最新设置的值显示到菜单上的作用
packages/apps/Settings/src/com/android/settings/DisplaySettings.java
import static android.provider.Settings.System.USER_ROTATION;
private static final int FALLBACK_SCREEN_ROTATION_VALUE = 0;
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
private ListPreference mScreenRotationPreference;
mScreenRotationPreference = (ListPreference) findPreference(KEY_SCREEN_ROTATION);
final long currentRotation = Settings.System.getLong(resolver, USER_ROTATION,
FALLBACK_SCREEN_ROTATION_VALUE);
mScreenRotationPreference.setValue(String.valueOf(currentRotation));
mScreenRotationPreference.setOnPreferenceChangeListener(this);
disableUnusableTimeouts(mScreenRotationPreference);
updateRotationPreferenceDescription(currentRotation);
private void updateRotationPreferenceDescription(long currentRotation) {
ListPreference preference = mScreenRotationPreference;
String summary;
// 处理不支持的值
if (currentRotation < 0) {
// Unsupported value
summary = "";
} else {
final CharSequence[] entries2 = preference.getEntries();
final CharSequence[] values2 = preference.getEntryValues();
if (entries2 == null || entries2.length == 0) {
summary = "";
} else {
int best = 0;
for (int i = 0; i < values2.length; i++) {
long rota = Long.parseLong(values2[i].toString());
if (currentRotation >= rota) {
best = i;
}
}
summary = preference.getContext().getString(R.string.screen_rotati on_summary,entries2[best]);
}
}
3.2.3.设置标签
添加了设置菜单选项,并为每个选项赋值:
packages/apps/Settings/res/values/arrays.xml
<!--Display settings.Srceen_rotation. add by Jangel-->
<string-array name="screen_rotation_entries">
<item>0° </item>
<item>90° </item>
<item>180° </item>
<item>270° </item>
</string-array>
<!-- Do not translate.这部分内容是取其具体数值,不要对其进行语言翻译 -->
<string-array name="screen_rotation_values" translatable="false">
<!-- Do not translate. -->
<item>0</item>
<!-- Do not translate. -->
<item>1</item>
<!-- Do not translate. -->
<item>2</item>
<!-- Do not translate. -->
<item>3</item>
</string-array>
3.2.4.添加菜单标题
packages/apps/Settings/res/values/strings.xml
<!--add by Jangel-->
<string name="screen_rotation">Rotation</string>
<string name="screen_rotation_summary">Rotate screen <xliff:g id="rotation_description">%1$s</xliff:g></string>
同理,需要对以上xml文件进行中文翻译,这里不再详述。
packages/apps/Settings/res/xml/display_settings.xml
<!--add by Jangel-->
<ListPreference
android:key="screen_rotation"
android:title="@string/screen_rotation"
android:summary="@string/screen_rotation_summary"
android:persistent="false"
android:entries="@array/screen_rotation_entries"
android:entryValues="@array/screen_rotation_values" />
4.应用程序设置屏幕旋转方法
//设置为横屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// 设置为竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
也可以通过xml实现:
//在配置文件中,设为横
android:screenOrientation="landscape"
//在配置文件中,设为竖屏
android:screenOrientation="portrait"