此需求是在开发学生模式时,客户提到的一项功能,当学生模式开启时,不允许安装应用,同时adb命令也不可安装应用,只有输入正确密码才可以安装,但是已经安装的apk,允许正常安装无需输入密码,也就是更新不需要输入密码。
首先我们定义一个开关的常量,当学生模式开启时启用此功能,如果不需要开关控制此处可不加。
路径:frameworks/base/core/java/android/provider/Settings.java
/**soda water Child model
* STUDENT_MODEL_SWITCH .
* @hide
*/
@Readable
public static final String STUDENT_MODEL_SWITCH = "student_model_switch";
添加需要用到的字符串资源 此处是密码输入失败时提示的消息和输入框的提示
路径:frameworks/base/packages/PackageInstaller/res/values/strings.xml
Incorrect password cannot be installed
Please enter your password
路径:frameworks/base/packages/PackageInstaller/res/values-zh-rCN/strings.xml
"密码错误 无法安装"
"请输入密码"
接着我们找到弹框的布局文件,在install_confirm_question_update后添加输入框
路径:frameworks/base/packages/PackageInstaller/res/layout/install_content_view.xml
此方法 startInstallConfirm() 是点击安装时触发的方法 ,在此方法中做如下修改 不需要开关控制的,可以删除对应student_model_switch的判断。
mOk.getText().toString().equals(getString(R.string.update) 此处用来判断是否未更新弹框如果是更新则可安装
路径:frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
//*/soda water Installation password 导入相关包
import android.widget.EditText;
import android.text.InputType;
import android.widget.Toast;
import android.content.ComponentName;
//*/
//*/soda water Installation password 定义输入框
private EditText studen_password;
//*/
private void startInstallConfirm() {
View viewToEnable;
//*/soda water Installation password 此处绑定控件
studen_password = requireViewById(R.id.edit_student_passwork);
if (mAppInfo != null) {
viewToEnable = requireViewById(R.id.install_confirm_question_update);
mOk.setText(R.string.update);
} else {
// This is a new application with no permissions.
viewToEnable = requireViewById(R.id.install_confirm_question);
//*/soda water Installation password 下面这段是当学生模式开启时显示输入框 不需要开关可删掉判断条件
boolean studentSwitch = Settings.Global.getInt(getApplicationContext().getContentResolver(), "student_model_switch",0) != 0;
if(studentSwitch){
studen_password.setInputType(InputType.TYPE_CLASS_NUMBER);
studen_password.setHint(R.string.student_hint_passwork);
studen_password.setVisibility(View.VISIBLE);
}
Log.d("soda water", "install_confirm_question ");
//*/
}
viewToEnable.setVisibility(View.VISIBLE);
mEnableOk = true;
mOk.setEnabled(true);
mOk.setFilterTouchesWhenObscured(true);
}
private void bindUi() {
mAlert.setIcon(mAppSnippet.icon);
mAlert.setTitle(mAppSnippet.label);
mAlert.setView(R.layout.install_content_view);
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
(ignored, ignored2) -> {
if (mOk.isEnabled()) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
//*/soda water Installation password 此处加入判断 开启开关时我们获取输入框内容和student_password_global的密码进行比较 不正确则弹框 正确则执行安装方法startInstall
String password = Settings.Global.getString(getApplicationContext().getContentResolver(), "student_password_global");
boolean studentSwitch = Settings.Global.getInt(getApplicationContext().getContentResolver(), "student_model_switch",0) != 0;
if(studentSwitch){
if(password.equals(studen_password.getText().toString()) || mOk.getText().toString().equals(getString(R.string.update))){
sendBroadcast();
Log.d("soda water", "bindUi aaa ");
startInstall();
Log.d("soda water", "bindUi bbb ");
}else if(!password.equals(studen_password.getText().toString())){
Toast.makeText(getApplicationContext(), R.string.student_install_no, Toast.LENGTH_SHORT).show();
}
}else{
startInstall();
}
Log.d("soda water", "bindUi-1 "+studen_password.getText().toString());
//*/
}
}
}, null);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
(ignored, ignored2) -> {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}, null);
setupAlert();
mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
mOk.setEnabled(false);
if (!mOk.isInTouchMode()) {
mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).requestFocus();
}
}
//*/soda water 这个自定义的广播是用来判断是手动安装还是adb安装 后面会详细解释 如果不需要判断adb安装看到这里已经OK了 不调用此方法即可
private void sendBroadcast(){
Intent intent = new Intent("action.phone.install");
ComponentName componentName = new ComponentName("com.example.studentmodel","com.example.studentmodel.BootReceiver");
intent.setComponent(componentName);
sendBroadcast(intent);
}
不需要判断adb安装看到此处即可
接着我们看上一处发送的广播实际上这里主要是因为不能调用Settings.Global.putInt我才写广播进行标记的,正常接收器写在mtksettings的任意接收器也可以。
上面在密码输入正确时发送广播phone_install状态变为1
我的代码如下:
AndroidManifest.xml
package com.example.studentmodel;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
public class BootReceiver extends BroadcastReceiver {
private Context mContext;
private static String ACTION_PHONE_INSTALL = "action.phone.install";
@Override
public void onReceive(Context context, Intent intent) {
mContext = context;
if (intent.getAction().equals(ACTION_PHONE_INSTALL)) {
Settings.Global.putInt(context.getContentResolver(), "phone_install", 1);
}
}
添加字符串资源
路径:frameworks/base/core/res/res/values/strings.xml
disables ADB installation applications
路径:frameworks/base/core/res/res/values-zh-rCN/strings.xml
"禁止ADB安装应用"
路径:frameworks/base/core/res/res/values/symbols.xml
接着我们来对adb安装进行限制 我们主要对doHandleMessage 方法中 INIT_COPY 做处理这是apk安装时一定会走到的 ,判断开关状态并且获取phone_install 的值判断是否为手动安装,如果是adb安装则执行return,这里我们接收前面提到的phone_install 在执行完判断后恢复为0。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
//*/soda water Kiddie mode applies mounting switch 导包
import com.android.internal.R;
import android.widget.Toast;
//*/
//*/soda water Kiddie mode applies mounting switch
private boolean APK_INSTALL_SWITCH = false;
//*/
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
//*/soda water Kiddie mode applies mounting switch
APK_INSTALL_SWITCH = Settings.Global.getInt(mPm.mContext.getContentResolver(),Settings.Global.STUDENT_MODEL_SWITCH,0) == 1;
boolean APK_INSTALL_IS_ADB = Settings.Global.getInt(mPm.mContext.getContentResolver(),"phone_install",0) == 0;
Log.d("soda water", "bindUi ccc "+Settings.Global.getInt(mPm.mContext.getContentResolver(),"phone_install",0));
if(APK_INSTALL_SWITCH && APK_INSTALL_IS_ADB){
Toast.makeText(mPm.mContext,R.string.student_model_adb_apk_install, Toast.LENGTH_SHORT).show();
return;
}
Settings.Global.putInt(mPm.mContext.getContentResolver(),"phone_install",0);
//*/soda water Kiddie mode applies mounting switch
HandlerParams params = (HandlerParams) msg.obj;
if (params != null) {
if (DEBUG_INSTALL) Slog.i(TAG, "init_copy: " + params);
Log.d("soda water","init_copy: "+params);
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(params));
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
params.startCopy();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
break;
}