06.输入系统:第10课第11节_输入系统_实战_使用GlobalKey一键启动程序_P

该课时开始讲解GlobalKey一键启动程序,一键启动的过程如下:
a.对于GlobalKey,系统会根据global_key.xml文件决定发送消息给那个组件
b.APP应该注册广播消息的接收者
1.编写一个BroadCastReceiver派生类,实现消息的处理函数
2.注册派生类
c.在该组件中启动APP
首先我们实现a,b两点,先写出一个能接收广播消息的应用程序,、

APP应用程序

我们先来实现上述的b,然后再实现a。

APP修改

我们在之前的APP_0001_LEDDemp-V3上进行修改,使用AS进入该工程,点击:
06.输入系统:第10课第11节_输入系统_实战_使用GlobalKey一键启动程序_P
弹出如下窗口:
06.输入系统:第10课第11节_输入系统_实战_使用GlobalKey一键启动程序_P
给需要创建的类命名为MyBroadcastReceiver,可以看到生成了一个新的文件,其内容为下:

public class MyBroadcastReceiver {
}

我们让该类继承于BroadcastReceiver,然后我们需要复写其成员onReceive,简单修改如下:

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"Get BroadcasstReceiver",Toast.LENGTH_SHORT).show();
    }
}

当接收到广播的时候会执行onReceive函数,Toast.makeText(context,“Get BroadcasstReceiver”,Toast.LENGTH_SHORT).show()为简单的显示一个提示框,其内容为"Get BroadcasstReceiver",下面我们修改app-> manifests-> AndroidManifest.xml文件,注册该APP为一个静态广播接收者,在<application下方,添加代码如下:

<receiver android:name=".MyBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.GLOBAL_BUTTON"/>
    </intent-filter>
</receiver>

其中的android:name="android.intent.action.GLOBAL_BUTTON"代表只有接收到GLOBAL_BUTTON这种广播,才会执行onReceive方法。

APP系统签名

发送一个ndroid.intent.action.GLOBAL_BUTTON的广播,是需要ACTION_MANAGE_WRITE_SETTINGS权限的, 在安卓6.0以后获取ACTION_MANAGE_WRITE_SETTINGS权限,需要通过用户进行确认,在onCreate中,我们添加如下获取权限代码:

       checkBoxLed4 = (CheckBox)findViewById(R.id.LED4);        
       button.setOnClickListener(new MyButtonListener());

+        //申请android.permission.WRITE_SETTINGS权限的方式
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            //如果当前平台版本大于23平台
+            if (!Settings.System.canWrite(this)) {
+                //如果没有修改系统的权限这请求修改系统的权限
+                Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
+                intent.setData(Uri.parse("package:" + getPackageName()));
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                startActivityForResult(intent, 0);
+            } else {
+                //有了权限,你要做什么呢?具体的动作
+               // processShow();
+            }
+        }

这样就能跳转到用户设置权限界面了,但是,我们运行APP的时候会发现,虽然跳转到了用户界面,选择的拖动图标,是灰色的,依然无法选择,因为在android 6.0及以后,WRITE_SETTINGS权限的保护等级已经由原来的dangerous升级为signature,这意味着我们的APP需要用系统签名或者成为系统预装软件才能够申请此权限,并且还需要提示用户跳转到修改系统的设置界面去授予此权限

那么下面我们就设置让我们的APP使用系统签名,获取系统权限,首先我们在linux中创建文件夹sigAPK,并且在该目录下创建文件keytool-importkeypair,编写内容如下(该为一个脚本文件):

#! /bin/bash
#
# This file is part of keytool-importkeypair.
#
# keytool-importkeypair is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# keytool-importkeypair is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with keytool-importkeypair.  If not, see
# <http://www.gnu.org/licenses/>.
#

DEFAULT_KEYSTORE=$HOME/.keystore
keystore=$DEFAULT_KEYSTORE
pk8=""
cert=""
alias=""
passphrase=""
tmpdir=""

scriptname=`basename $0`

usage() {
cat << EOF
usage: ${scriptname} [-k keystore] [-p storepass]
-pk8 pk8 -cert cert -alias key_alias

This script is used to import a key/certificate pair
into a Java keystore.

If a keystore is not specified then the key pair is imported into
~/.keystore in the user's home directory.

The passphrase can also be read from stdin.
EOF
}

cleanup() {
if [ ! -z "${tmpdir}" -a -d ${tmpdir} ]; then
   rm -fr ${tmpdir}
fi
}

while [ $# -gt 0 ]; do
        case $1
        in
                -p | --passphrase | -passphrase)
                        passphrase=$2
                        shift 2
        ;;
                -h | --help)
                        usage
                        exit 0
        ;;
                -k | -keystore | --keystore)
                        keystore=$2
                        shift 2
        ;;
                -pk8 | --pk8 | -key | --key)
                        pk8=$2
                        shift 2
        ;;
                -cert | --cert | -pem | --pem)
                        cert=$2
                        shift 2
        ;;
                -a | -alias | --alias)
                        alias=$2
                        shift 2
        ;;
                *)
                        echo "${scriptname}: Unknown option $1, exiting" 1>&2
                        usage
                        exit 1
        ;;
        esac
done

if [ -z "${pk8}" -o -z "${cert}" -o -z "${alias}" ]; then
   echo "${scriptname}: Missing option, exiting..." 1>&2
   usage
   exit 1
fi


for f in "${pk8}" "${cert}"; do
    if [ ! -f "$f" ]; then
       echo "${scriptname}: Can't find file $f, exiting..." 1>&2
       exit 1
    fi
done

if [ ! -f "${keystore}" ]; then
   storedir=`dirname "${keystore}"`
   if [ ! -d "${storedir}" -o ! -w "${storedir}" ]; then
      echo "${scriptname}: Can't access ${storedir}, exiting..." 1>&2
      exit 1
   fi
fi

# Create temp directory ofr key and pkcs12 bundle
tmpdir=`mktemp -q -d "/tmp/${scriptname}.XXXX"`

if [ $? -ne 0 ]; then
   echo "${scriptname}: Can't create temp directory, exiting..." 1>&2
   exit 1
fi

key="${tmpdir}/key"
p12="${tmpdir}/p12"

if [ -z "${passphrase}" ]; then
   # Request a passphrase
  read -p "Enter a passphrase: " -s passphrase
  echo ""
fi

# Convert PK8 to PEM KEY
openssl pkcs8 -inform DER -nocrypt -in "${pk8}" -out "${key}"

# Bundle CERT and KEY
openssl pkcs12 -export -in "${cert}" -inkey "${key}" -out "${p12}" -password pass:"${passphrase}" -name "${alias}"

# Print cert
echo -n "Importing \"${alias}\" with "
openssl x509 -noout -fingerprint -in "${cert}"

# Import P12 in Keystore
keytool -importkeystore -deststorepass "${passphrase}" -destkeystore "${keystore}" -srckeystore "${p12}" -srcstoretype PKCS12 -srcstorepass "${passphrase}" 

# Cleanup
cleanup

编写完成之后,退出保存,然后拷贝build/target/product/security/目录下的platform.pk8,platform.x509.pem到自己创建的sigAPK目录中,然后在linux环境下执行

./keytool-importkeypair -k SignKitking.jks -p 123456 -pk8 platform.pk8 -cert platform.x509.pem -alias SignKitking

该命令的格式如下:

./keytool-importkeypair -k [jks文件名] -p [jks的密码] -pk8 platform.pk8 -cert platform.x509.pem -alias [jks的别名]

执行之后,我们可以看到生成SignKitking.jks文件,然后拷贝sigAPK目录到AS工程目录,如下:
06.输入系统:第10课第11节_输入系统_实战_使用GlobalKey一键启动程序_P
该些工作完之后我们还需要配置AS工程,首先在AndroidManifest.xml文件中添加如下代码(与<application同级),为获取系统权限:

android:sharedUserId="android.uid.system" >

在build.gradle(Module:app)的android 下添加:

    signingConfigs {
        release {
            storeFile file('C:/AndroidStudio/3.workProject/andriod7.1APP/APP6/APP_0001_LEDDemp-V3/sigAPK/SignKitking.jks')
            storePassword '123456'
            keyAlias 'SignKitking'
            keyPassword '123456'
        }
        debug {
            storeFile file('C:/AndroidStudio/3.workProject/andriod7.1APP/APP6/APP_0001_LEDDemp-V3/sigAPK/SignKitking.jks')
            storePassword '123456'
            keyAlias 'SignKitking'
            keyPassword '123456'
        }
    }

该处的路基大家不要着急,暂时这样填写,然后选择:
06.输入系统:第10课第11节_输入系统_实战_使用GlobalKey一键启动程序_P
进入:
06.输入系统:第10课第11节_输入系统_实战_使用GlobalKey一键启动程序_P
选择Signing,然后重新配置路径,接下来下面的是重点,请大家注意:
06.输入系统:第10课第11节_输入系统_实战_使用GlobalKey一键启动程序_P
选择BuildTypes之后,一定要配置SigningConfig为debug(一定要配置,一定要配置)。

以上所有动作完成之后,重新编译APP,如果编译出错,可以在build.gradle(Module:app)的android中添加:

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            signingConfig signingConfigs.debug
        }
    }

当然,或许解决不了你的根本问题,那么可以另行百度,下面我们继续修改APP。

发送广播)

完成以上步骤之后,我们开启开发板,运行APP,在串口终端输入su,进入管理模式:首先运行一次(不知道具体原因):

am broadcast -a 1 -n com.example.administrator.app_0001_leddemp/.MyBroadcastReceiver

随便发送一个广播(或许此广播为错误广播),然后再发送GLOBAL_BUTTON广播(多次发送)

am broadcast -a android.intent.action.GLOBAL_BUTTON -n com.example.administrator.app_0001_leddemp/.MyBroadcastReceiver

我们可以看到开发板会出现黑色提示框

除了以上发送广播的方法,还可以在MainActivity.java文件的public void onClick(View view) 方法中,添加如下代码:

  public void onClick(View view) {
       button.setText("ALL OFF");
       ledon = !ledon;

+      Intent intent = new Intent("android.intent.action.GLOBAL_BUTTON");
+      intent.putExtra("msg", "hello receiver.");
+      sendBroadcast(intent);

这样每次点击all按钮,就会发送一个广播,能看到接收到广播的提示。这样就证明我们可以接收到广播了(当程序退出的时候,也能接收到),下面我们在收到广播执行的函数MyBroadcastReceiver.onReceive方法中去实现启动APP。

启动APP

在MyBroadcastReceiver.onReceive方法中添加如下代码:

    Intent intentNewTask =new Intent(context, MainActivity.class);
    intentNewTask.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intentNewTask);

其中MainActivity.class就是要启动的应用程序,添加之后进行编译,然后在开发板上运行APP,启动之后我们再推出APP,然后执行:

am broadcast -a 1 -n com.example.administrator.app_0001_leddemp/.MyBroadcastReceiver

随便发送一个广播(或许此广播为错误广播),然后再发送GLOBAL_BUTTON广播(多次发送)

am broadcast -a android.intent.action.GLOBAL_BUTTON -n com.example.administrator.app_0001_leddemp/.MyBroadcastReceiver

这样我们可以看到APP自动启动,到此为止,应用程程序我们已经编写完成,即最开头的b步骤我们已经完成,下面倒过来完成a步骤。

底层修改

首先我们打开源码中的SDK\frameworks\base\core\res\res\xml\global_keys.xml文件,该文件原内容如下:

<global_keys version="1">
    <!-- Example format: keyCode = keycode to handle globally. component = component which will handle this key. -->
    <!-- <key keyCode="KEYCODE_VOLUME_UP" component="com.android.example.keys/.VolumeKeyHandler" /> -->
</global_keys>

其中前面的 <!-- 表示注释,我们添加以下内容:

<global_keys version="1">
    <!-- Example format: keyCode = keycode to handle globally. component = component which will handle this key. -->
    <!-- <key keyCode="KEYCODE_VOLUME_UP" component="com.android.example.keys/.VolumeKeyHandler" /> -->
+	<key keyCode="KEYCODE_TV" component="com.example.administrator.app_0001_leddemp/.MyBroadcastReceiver" />
</global_keys>

表示,当我们按下KEYCODE_TV按钮,就会发送广播给com.example.administrator.app_0001_leddemp/.MyBroadcastReceiver,然后启动应用程序。

修改完成之后,执行
mmm frameworks/base/core/res/编译,他会生成out/target/product/qytech_azalea/system/framework/framework-res.apk,
然后执行
make snod -j8
重新烧写system.img文件
启动开发板,安装app之后,在终端输入:
input keyevent 170
可以看到APP自动启动,其中上报170是因为在源码中:

field public static final int KEYCODE_TV = 170; // 0xaa

所以我们上报170,相当于按下了KEYCODE_TV按键,或者执行:
input keyevent TV
也能达到同样的效果