最近做个ANDROID项目,需要屏蔽平板底层的导航按钮,难点是APP里不仅有自己写的activity,还有调用别的APP(GSF)里的activity里,不是很好实现,先将已有的不完善的解决方法写出来。
所有的实现都在NEXSU 7上测试的,别的机子没测过,不保证有用。
下面介绍的第五种方法是现在最好的解决方法。通过反射机制获取隐藏服务StatusBarService,调用它的disable()方法来屏蔽导航按钮。
一. 自己写的activity屏蔽导航按钮
1. 获取到界面上的一个view
2. 调用API
view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
3. 不过Google认为导航按钮太重要了,当你点击屏幕的时候,导航按钮会再次显示出来。
我尝试的监听导航按钮显示事件,当收到显示事件后,再次调用上面的API来隐藏,不过这样显示有问题,屏幕有抖动。
view.setOnSystemUiVisibilityChangeListener(mSystemUiVisibilityChangeListener);
View.OnSystemUiVisibilityChangeListener mSystemUiVisibilityChangeListener
= new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int vis) {
if ((vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0) { //当前状态是显示,隐藏它
view.setSystemUiVisibility (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
}
}
4. 由于目前还没有好的解决上面的问题,我就不去掉导航按钮整个一栏,而是仅仅去掉导航按钮
int INVISBLE = 0x00400000|0x00200000|0x01000000|0x00010000;//这几个整数参数API是不开放的
//INVISBLE = View.STATUS_BAR_DISABLE_EXPAND | View.STATUS_BAR_DISABLE_RECENT|View.STATUS_BAR_DISABLE_BACK|View.STATUS_BAR_DISABLE_HOME
view.setSystemUiVisibility(INVISBLE);
二. 调用第三方APK里的activity
由于需要GOOGLE的登录界面,我就调用了GSF提供的登录activity,方法如下:
Intent localIntent = new Intent(com.google.android.accounts.AccountIntro);
localIntent.putExtra("firstRun", true);
localIntent.putExtra("allowSkip", true);
startActivityForResult(localIntent, requestCode);
但是当登录界面弹出对话框时,导航按钮都显示出来了,这个是客户接受不了的,要我必须改掉,目前还没有解决掉(已经解决)。问题界面如下
目前想出来的方法是将下面导航栏都去掉,有三种方法,当必须都已经ROOT了。
两种方法屏蔽掉导航按钮,不需要ROOT。
第一种:
修改system/build.prop 添加系统属性 qemu.hw.mainkeys=1,重启。
缺点: 需要重启,所以就没有采用。
第二种:
删除system/app/SystemUI.apk ,这个就是用来显示系统导航栏的APK,但在需要的时候,将APK在放到app目录下,启动服务就行了。
在APK开始运行的时候,将SystemUI.apk移动另一个地方,当APK运行结束的时候,在将SystemUI.apk移回原位,启动SystemUIService服务。
源码如下:
protected static void moveSystemUIapkFile(boolean move) {
try {
File systemUIapkFile = new File("/system/app/SystemUI.apk");
Process p;
p = Runtime.getRuntime().exec("su");
// Attempt to write a file to a root-only
DataOutputStream os = new DataOutputStream(p.getOutputStream());
os.writeBytes("mount -o remount,rw /dev/block/stl6 /system\n");
if (move && systemUIapkFile.exists()) {
os.writeBytes("mv /system/app/SystemUI.apk /system/SystemUI.apk\n");
}
if (!move && !systemUIapkFile.exists()){
os.writeBytes("mv /system/SystemUI.apk /system/app/SystemUI.apk\n");
os.writeBytes("LD_LIBRARY_PATH=/vendor/lib:/system/lib am startservice -n com.android.systemui/.SystemUIService");
}
os.writeBytes("mount -o remount,ro /dev/block/stl6 /system\n");
// Close the terminal
os.writeBytes("exit\n");
os.flush();
p.waitFor();
} catch (Exception e) {
Log.e(TAG,"moveSystemUIapkFile",e);
}
}
先在开始的时候调用
moveSystemUIapkFile(true);
再在结束的时候调用
moveSystemUIapkFile(false);
缺点: 当将SystemUI.apk移走时,平板会重启,重启后也就没有了导航栏。需要重启,也没有采用。
第三钟:
将系统进程com.android.systemui杀掉,不过系统会自动将它重启,那就循环的KILL。
实验下来循环时间在300MS左右是最好的,时间长了,屏幕就会由抖动现象。
首先将 killall 和 usleep 这两个命令程序拷贝到asserts目录下,源码和命令程序在这里下载。
一部分源码如下:
package com.dstvmobile.walkatouch.setupwizard;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Map;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.util.Log;
public enum Device {
INSTANCE;
private static String TAG = Device.class.getSimpleName();
private boolean mHasRootBeenChecked = false;
private boolean mIsDeviceRooted = false;
private boolean mHasBeenInitialized = false;
private Context mAppContext = null;
private boolean mSystembarVisible = true;
static public void initialize(Context appContext) {
if (INSTANCE.mHasBeenInitialized == true) {
Log.e(TAG, "Initializing already initialized class " + TAG);
}
INSTANCE.mHasBeenInitialized = true;
INSTANCE.mAppContext = appContext;
AddKillAll(appContext, "killall");
AddKillAll(appContext, "usleep");
// moveSystemUIapkFile(true);
}
public static void AddKillAll(Context appContext, String commandFileName) {
File killAllFile = new File("/system/xbin/"+commandFileName);
if (!killAllFile.exists()) {
AssetManager assetManager = appContext.getAssets();
InputStream inputStream = null;
String commandFilePath = null;
try {
inputStream = assetManager.open(commandFileName);
commandFilePath = appContext.getApplicationContext().getFilesDir()
.getAbsolutePath() + File.separator + commandFileName;
saveToFile(commandFilePath, inputStream);
} catch (IOException e) {
Log.e("tag", e.toString());
}
try {
Process p;
p = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(p.getOutputStream());
os.writeBytes("mount -o remount,rw /dev/block/stl6 /system\n");
os.writeBytes("cd system/xbin\n");
os.writeBytes("cat " + commandFilePath + " > " + commandFileName + "\n");
os.writeBytes("chmod 755 " + commandFileName + "\n");
os.writeBytes("mount -o remount,ro /dev/block/stl6 /system\n");
os.writeBytes("exit\n");
os.flush();
p.waitFor();
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
}
static public Device getInstance() {
INSTANCE.checkInitialized();
return INSTANCE;
}
private void checkInitialized() {
if (mHasBeenInitialized == false)
throw new IllegalStateException("Singleton class " + TAG
+ " is not yet initialized");
}
public boolean isRooted() {
checkInitialized();
if (mHasRootBeenChecked) {
return mIsDeviceRooted;
}
try {
File file = new File("/system/app/Superuser.apk");
if (file.exists()) {
mHasRootBeenChecked = true;
mIsDeviceRooted = true;
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
try {
ArrayList<String> envlist = new ArrayList<String>();
Map<String, String> env = System.getenv();
for (String envName : env.keySet()) {
envlist.add(envName + "=" + env.get(envName));
}
String[] envp = (String[]) envlist.toArray(new String[0]);
Process proc = Runtime.getRuntime()
.exec(new String[] { "which", "su" }, envp);
BufferedReader in = new BufferedReader(new InputStreamReader(
proc.getInputStream()));
if (in.readLine() != null) {
mHasRootBeenChecked = true;
mIsDeviceRooted = true;
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
mHasRootBeenChecked = true;
mIsDeviceRooted = false;
return false;
}
public enum AndroidVersion {
HC, ICS, JB, UNKNOWN
};
public AndroidVersion getAndroidVersion() {
checkInitialized();
int sdk = android.os.Build.VERSION.SDK_INT;
if (11 <= sdk && sdk <= 13) {
return AndroidVersion.HC;
} else if (14 <= sdk && sdk <= 15) {
return AndroidVersion.ICS;
} else if (16 == sdk) {
return AndroidVersion.JB;
} else {
return AndroidVersion.UNKNOWN;
}
}
protected static void moveSystemUIapkFile(boolean move) {
try {
File systemUIapkFile = new File("/system/app/SystemUI.apk");
Process p;
p = Runtime.getRuntime().exec("su");
// Attempt to write a file to a root-only
DataOutputStream os = new DataOutputStream(p.getOutputStream());
os.writeBytes("mount -o remount,rw /dev/block/stl6 /system\n");
if (move && systemUIapkFile.exists()) {
os.writeBytes("cp /system/app/SystemUI.apk /system/SystemUI.apk\n");
}
if (!move && !systemUIapkFile.exists()){
os.writeBytes("cp /system/SystemUI.apk /system/app/SystemUI.apk\n");
}
os.writeBytes("mount -o remount,ro /dev/block/stl6 /system\n");
// Close the terminal
os.writeBytes("exit\n");
os.flush();
p.waitFor();
if(!move){
}
} catch (Exception e) {
Log.e(TAG,"moveSystemUIapkFile",e);
}
}
public void showSystembar(boolean makeVisible) {
checkInitialized();
try {
ArrayList<String> envlist = new ArrayList<String>();
Map<String, String> env = System.getenv();
for (String envName : env.keySet()) {
envlist.add(envName + "=" + env.get(envName));
}
String[] envp = (String[]) envlist.toArray(new String[0]);
if (makeVisible) {
String command;
Device dev = Device.getInstance();
if (dev.getAndroidVersion() == AndroidVersion.HC) {
command = "LD_LIBRARY_PATH=/vendor/lib:/system/lib am startservice -n com.android.systemui/.SystemUIService";
} else {
command = ""
+"rm /sdcard/hidebar-lock\n"
+ "sleep 5\n"
+ "LD_LIBRARY_PATH=/vendor/lib:/system/lib am startservice -n com.android.systemui/.SystemUIService";
}
Runtime.getRuntime().exec(new String[] { "su", "-c", command }, envp);
mSystembarVisible = true;
} else {
String command;
Device dev = Device.getInstance();
if (dev.getAndroidVersion() == AndroidVersion.HC) {
command = "LD_LIBRARY_PATH=/vendor/lib:/system/lib service call activity 79 s16 com.android.systemui";
} else {
command = "touch /sdcard/hidebar-lock\n"
+ "while [ -f /sdcard/hidebar-lock ]\n"
+ "do\n"
+ "killall com.android.systemui\n"
+ "usleep 300000\n"
+ "done\n"
+ "LD_LIBRARY_PATH=/vendor/lib:/system/lib am startservice -n com.android.systemui/.SystemUIService";
}
Runtime.getRuntime().exec(new String[] { "su", "-c", command }, envp);
mSystembarVisible = false;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean isSystembarVisible() {
checkInitialized();
return mSystembarVisible;
}
public void sendBackEvent() {
try {
ArrayList<String> envlist = new ArrayList<String>();
Map<String, String> env = System.getenv();
for (String envName : env.keySet()) {
envlist.add(envName + "=" + env.get(envName));
}
String[] envp = (String[]) envlist.toArray(new String[0]);
Runtime.getRuntime().exec(
new String[] { "su", "-c",
"LD_LIBRARY_PATH=/vendor/lib:/system/lib input keyevent 4" },
envp);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void saveToFile(String filePath, InputStream in){
FileOutputStream fos = null;
BufferedInputStream bis = null;
int BUFFER_SIZE = 1024;
byte[] buf = new byte[BUFFER_SIZE];
int size = 0;
bis = new BufferedInputStream(in);
try {
fos = new FileOutputStream(filePath);
while ((size = bis.read(buf)) != -1)
fos.write(buf, 0, size);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
if (bis != null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在需要隐藏时候调度
Device.initialize(this);
Device.getInstance().showSystembar(false);
在需要去消隐藏的时候调用
Device.getInstance().showSystembar(true);
效果界面:
目前在研究如何屏蔽第三方APK界面的三个导航按钮,而不是去掉整个导航栏。
刚发现了屏蔽三个导航按钮的方案。
第四种:
使用StatusBarManager类,不过这个类是隐藏的,必须在源码下编译,使用SDK编译的肯定是找不到类。
import android.app.StatusBarManager;
mStatusBarManager = (StatusBarManager)this.getSystemService(Context.STATUS_BAR_SERVICE);
在需要隐藏的时候调用:
mStatusBarManager.disable(StatusBarManager.DISABLE_HOME
|StatusBarManager.DISABLE_BACK|StatusBarManager.DISABLE_RECENT);
在需要显示的时候调度:
mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
第五种:
对于第四种方法,SDK编译下也可以实现,使用JAVA的反射机制,获取隐藏接口。
这个方法在我的另一篇文章里已经介绍过了,来实现关机功能 http://blog.csdn.net/kobeyxyx/article/details/9112641。
private void statusBarDisable(boolean disable) {
try {
int DISABLE_NAVIGATION = 0x00200000 | 0x00400000 | 0x01000000;
//int DISABLE_NAVIGATION = View.STATUS_BAR_DISABLE_HOME | View.STATUS_BAR_DISABLE_BACK | View.STATUS_BAR_DISABLE_RECENT;
int DISABLE_NONE = 0x00000000;
//获得ServiceManager类
Class<?> ServiceManager = Class
.forName("android.os.ServiceManager");
//获得ServiceManager的getService方法
Method getService = ServiceManager.getMethod("getService", java.lang.String.class);
//调用getService获取RemoteService
Object oRemoteService = getService.invoke(null,"statusbar");
//获得IStatusBarService.Stub类
Class<?> cStub = Class
.forName("com.android.internal.statusbar.IStatusBarService$Stub");
//获得asInterface方法
Method asInterface = cStub.getMethod("asInterface", android.os.IBinder.class);
//调用asInterface方法获取IStatusBarService对象
Object oIStatusBarService = asInterface.invoke(null, oRemoteService);
//获得disable()方法
Method disableMethod = oIStatusBarService.getClass().getMethod("disable",int.class,IBinder.class,String.class);
//调用disable()方法
if(disable){
disableMethod.invoke(oIStatusBarService,DISABLE_NAVIGATION,new Binder(),this.getPackageName());
}else{
disableMethod.invoke(oIStatusBarService,DISABLE_NONE,new Binder(),this.getPackageName());
}
}catch (Exception e) {
Log.e(TAG, e.toString(), e);
}
}