精华内容
下载资源
问答
  • 安卓获取app禁用的组件
    千次阅读
    2019-04-18 12:31:03

    app里有禁用其他第三方app组件的需求,但发现组件被禁用以后,用常用的方法无法获取被禁用的组件,代码如下:

    PackageManager packagemanager =this.getPackageManager(); // 获得PackageManager对象 
    PackageInfo packageinfo = packagemanager.getPackageInfo("com.gunnrose.chaturbate",packagemanager.GET_ACTIVITIES);
    ActivityInfo[]AS= packageinfo.activities;
    if(AS!=null){
    for(int i=0;i<AS.length;i++){
       Log.d("组件名",AS[i].name);
    }
    }

     

    因此采用了迂回的方式解决这个问题,/ data/app/包名/base.apk是可读的,就可以用packagemanager.getPackageArchiveInfo()这个方法获取组件,应用包的路径可以用packageinfo.applicationInfo.publicSourceDir获取

    PackageManager packagemanager =this.getPackageManager(); // 获得PackageManager对象
    PackageInfo packageinfo = packagemanager.getPackageArchiveInfo("/data/app/com.gunnrose.chaturbate-1/base.apk",packagemanager.GET_ACTIVITIES);  
    ActivityInfo[]AS= packageinfo.activities;
    if(AS!=null){
    for(int i=0;i<AS.length;i++){
        Log.d("组件名",AS[i].name);
    }
    }

     

    更多相关内容
  • 所以期望有一款app能代为管理,上班期间拍照/录像的时候会自动提示或者功能不可用,下班期间自动放开限制。 方案设计 可选方案1:接收NEW_PICTURE事件广播 首先自然而然地想到广播,相机拍照时系统会发出action...

    目录

    背景

    方案设计

    主要实现

    待完善

    完整源码

    背景

    公司安全政策限制,上班期间不能拍照/录像。虽然已经差不多养成习惯,但老虎保不住也有打盹的时候,曾经有一次就差点违规。所以期望有一款app能代为管理,上班期间拍照/录像的时候会自动提示或者功能不可用,下班期间自动放开限制。

    方案设计

    可选方案1:接收NEW_PICTURE事件广播

    首先自然而然地想到广播,相机拍照时系统会发出action为com.android.camera.NEW_PICTURE的广播,可以创建一个接收器在接收到这个广播时提示不允许拍照。AndroidManifest.xml文件中对广播接收器做静态注册,如下:

    <!-- AndroidManifest.xml -->
    ​
    <receiver
              android:name=".CameraEventReceiver"
              android:enabled="true">
      <intent-filter>
        <action android:name="com.android.camera.NEW_PICTURE" />
      </intent-filter>
    </receiver>

    问题是,这里的NEW_PICTURE是点击拍照按钮产生的广播事件,找了一圈没有找到打开相机产生的广播事件,这时拍照已成既定违规事实了,不能起到事前/事中提醒或禁止作用,不符合要求。

    可选方案2:CameraManager的onCameraUnavailable回调

    CameraManager 是系统服务之一,专门用于 检测打开相机以及获取相机设备特性。可以使用onCameraUnavailable回调函数在相机被占用时(意味着打开了相机)进行提醒。核心代码如下:

    CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    manager.registerAvailabilityCallback(new CameraManager.AvailabilityCallback() {
      @Override
      public void onCameraUnavailable(String cameraId) {
        super.onCameraUnavailable(cameraId);
        Log.i(TAG, "camera unavailable");
        // 提示相机不可用等弹框
    }

    功能上没问题,只是要保证服务一直在后台运行,才能在需要的时候提醒用户。其实安装这个App只是起到辅助提醒作用,真正需要提醒用户的概率很小很小,一直让App在后台运行太浪费手机资源。而且这个提醒功能只能做到事中提醒,能不能直接把相机直接禁用掉,事前防范,让用户没有犯错的机会呢。

    可选方案3(最终方案):Android Device Administration API禁用相机功能

    Android Device Administration API 是Android 用来提供企业应用支持的,通过API可以在系统级别提供密码管理、停用相机等设备管理功能。通过调用API,打开相机时给予以下错误提示:

    再配合闹钟(AlarmManager)实现定时开关,这恰恰就是我想要的,完美!

    主要实现

    1. 启用Device Administration API (MainActivity)

    DevicePolicyManager devicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
    ComponentName deviceAdmin = new ComponentName(this, MyDeviceAdminReceiver.class);
    ​
    if (!(devicePolicyManager.isAdminActive(deviceAdmin))) {
      // 启用Device Admin API
      startActivateDeviceAdminActivityForResult();
    } else {
      CameraUtil.blockOrUnblockCameraNow(this, amStartWorkTime, amStopWorkTime, pmStartWorkTime, pmStopWorkTime);
    }
    ​
    private void startActivateDeviceAdminActivityForResult() {
      Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
      intent.putExtra(
        DevicePolicyManager.EXTRA_DEVICE_ADMIN,
        deviceAdmin);
      intent.putExtra(
        DevicePolicyManager.EXTRA_ADD_EXPLANATION,
        getString(R.string.admin_explanation));
      startActivityForResult(intent, REQUEST_ENABLE);
    }

    2. 根据当前是否在工作时间段,发送启用/禁用相机广播

    public class CameraUtil {
      public static void blockOrUnblockCameraNow(Context context, int amStartWorkTime, int amStopWorkTime, int pmStartWorkTime, int pmStopWorkTime) {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
        intent.setComponent(new ComponentName("com.aniu.cameramanager","com.aniu.cameramanager.AlarmReceiver"));
        if (WorkTime.isNowWorkTime(amStartWorkTime, amStopWorkTime, pmStartWorkTime, pmStopWorkTime)) {
          context.sendBroadcast(intent.setAction("ACTION_BLOCK_CAMERA"));
        } else {
          context.sendBroadcast(intent.setAction("ACTION_UNBLOCK_CAMERA"));
        }
      }
    }

    3. 根据广播启用/禁用相机 ,并设置下一次的闹钟 (AlarmReceiver)

    public void onReceive(Context context, Intent intent) {
      if (action.equals("ACTION_BLOCK_CAMERA")) {
        devicePolicyManager.setCameraDisabled(deviceAdmin, true);
        Log.i(TAG, "禁用相机成功, action: " + action);
    ​
        // 设置下一次的闹钟
        setCameraAlarm(context, getNextCameraAlarm());
      } else if (action.equals("ACTION_UNBLOCK_CAMERA")) {
        devicePolicyManager.setCameraDisabled(deviceAdmin, false);
        Log.i(TAG, "启用相机成功, action: " + action);
    ​
        // 设置下一次的闹钟
        setCameraAlarm(context, getNextCameraAlarm());
      }
    }
    ​
    private void setCameraAlarm(Context context, Alarm alarm) {
      Intent intent = new Intent(context, alarm.getAlarmReceiver());
      intent.setAction(alarm.getAction());
      intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
      intent.setComponent(new ComponentName("com.aniu.cameramanager","com.aniu.cameramanager.AlarmReceiver"));
      PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 999, intent, 0);
      Calendar calendar = alarm.getCalendar();
    ​
      AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
      alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
      Log.i(TAG, "设置闹钟成功: " + alarm.getCalendar().getTime() + " " + alarm.getAction());
    }

    实现过程中的坑点和对应处理

    1. 原计划使用AlarmManager的循环闹钟,但目前(2021-6-8)情况下循环闹钟的事件都是不精确的,在华为畅享9(我的主力测试机)上实测差几分钟/几十分钟的情况都存在,所以使用了单个闹钟AlarmManager.setExactAndAllowWhileIdle方法,低功耗下也可以准确执行。方法的第一个参数使用AlarmManager.RTC_WAKEUP,即以系统时间为参照。

    2. 广播定向发送

      在很多手机上应用发送广播需要申请权限或者特殊处理,不然会接收不到广播,这里设置成了定向广播:

      intent.setComponent(new ComponentName("com.aniu.cameramanager","com.aniu.cameramanager.AlarmReceiver"));

    3. 重启处理

      手机重启后需要让应用保持运行,需要在AlarmReceiver中同时监听开机事件。

      if (action.equals("android.intent.action.BOOT_COMPLETED")) {
        Log.i(TAG, "监听到重启完成事件, action: " + action);
      ​
        // 重启后根据当前时间段禁用/启用相机
        CameraUtil.blockOrUnblockCameraNow(context, amStartWorkTime, amStopWorkTime, pmStartWorkTime, pmStopWorkTime);
      } 

      同时在manifest文件中receiver的intent-filter中增加action android:name="android.intent.action.BOOT_COMPLETED" 。

    4. 保持后台运行

      如果不把应用设置成可后台运行,监听开机事件拉起receiver后很快应用进程就会被系统杀掉,需要提供入口或引导用户设置应用保持后台运行。

    5. 防止被意外强行终止

      很多手机用户喜欢清理最近使用的应用,这里在manifest文件中设置android:excludeFromRecents="true"属性来规避。

    待完善

    增加临时启用相机入口

    虽然程序中增加了当天是否为工作日的判断,节假日不会去停用相机,但如果用户休假,那在原工作时间段内相机还是会停用,导致用户没办法使用相机。需要在提示用户相机被停用的界面增加临时启用相机入口,不妨碍用户正常使用相机。

    目前还不知道怎么修改这个界面,在stackoverflow上面提了问题,还没人回复。

    完整源码

    https://gitee.com/teng-aniu/CemaraManager

    展开全文
  • Android8.0中实现APP禁用模式(一)

    千次阅读 2019-03-16 11:24:38
    产品经理要求在Android平板中实现一个应用的禁用模式。当一个已安装的应用被设置为禁用的时候,在启动器中,APP图标灰色,且APP不能启动。 需求分析 打开一个APP的方式有三种:1、从启动器点击图标启动;2、点击...

    需求

    产品经理要求在Android平板中实现一个应用的禁用模式。当一个已安装的应用被设置为禁用的时候,在启动器中,APP图标灰色,且APP不能启动。

    需求分析

    打开一个APP的方式有三种:1、从启动器点击图标启动;2、点击APP弹出的通知启动;3、点击多任务键,选择APP。这三种启动方式中,第一个很容易实现禁用,只需要修改launcher就行,在图标的点击事件处理中增加一点逻辑即可。这里就分析一下如何在第二种和第三种启动方式中实现禁用模式。

    数据传递

    哪个APP被禁用是APP设置的,所以禁用信息要从APP传递到framework中。这个信息不仅要能get到,而且要能实时的监听。要做到这种跨进程的数据传递,做好的方式就是通过ContentProvider。

    NotificationManagerService

    Android中发送通知的方法在NotificationManager中,所以我们从NotificationManager开始找代码。NotificationManager中的notify方法有如下三个

    public void notify(int id, Notification notification)
    {
        notify(null, id, notification);
    }
    
    public void notify(String tag, int id, Notification notification)
    {
        notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
    }
    
    public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    {
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        // Fix the notification as best we can.
        Notification.addFieldsFromContext(mContext, notification);
        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                notification.sound.checkFileUriExposed("Notification.sound");
            }
        }
        fixLegacySmallIcon(notification, pkg);
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }
        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
        notification.reduceImageSizes(mContext);
        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        boolean isLowRam = am.isLowRamDevice();
        final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam);
        // 关键在这里
        try {
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    copy, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    

    可以看到,这三个方法实际上最终都调用到了notifyAsUser方法中。这个方法的前面是一些参数的检查,关键的内容是下面这行:

    service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, copy, user.getIdentifier());
    

    service的类型是INotificationManager接口,熟悉Framework的朋友都知道,这是一个aidl接口,其实现就是xxxService。这里就是NotificationManagerService。下面就看下NotificationManagerService的enqueueNotificationWithTag方法。

    @Override
    public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
            Notification notification, int userId) throws RemoteException {
        enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
                Binder.getCallingPid(), tag, id, notification, userId);
    }
    

    这里实际调用了enqueueNotificationInternal方法。这种xxxInternal的函数命名方式也是Android的常规操作了。

    void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
    		final int callingPid, final String tag, final int id, final Notification notification,
    		int incomingUserId) {
    	if (DBG) {
    		Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
    				+ " notification=" + notification);
    	}
    	checkCallerIsSystemOrSameApp(pkg);
    
    	final int userId = ActivityManager.handleIncomingUser(callingPid,
    			callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
    	final UserHandle user = new UserHandle(userId);
    
    	if (pkg == null || notification == null) {
    		throw new IllegalArgumentException("null not allowed: pkg=" + pkg
    				+ " id=" + id + " notification=" + notification);
    	}
    
    	// The system can post notifications for any package, let us resolve that.
    	final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);
    
    	// Fix the notification as best we can.
    	try {
    		final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
    				pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
    				(userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
    		Notification.addFieldsFromContext(ai, notification);
    
    		int canColorize = mPackageManagerClient.checkPermission(
    				android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
    		if (canColorize == PERMISSION_GRANTED) {
    			notification.flags |= Notification.FLAG_CAN_COLORIZE;
    		} else {
    			notification.flags &= ~Notification.FLAG_CAN_COLORIZE;
    		}
    
    	} catch (NameNotFoundException e) {
    		Slog.e(TAG, "Cannot create a context for sending app", e);
    		return;
    	}
    
    	mUsageStats.registerEnqueuedByApp(pkg);
    
    	// setup local book-keeping
    	String channelId = notification.getChannelId();
    	if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
    		channelId = (new Notification.TvExtender(notification)).getChannelId();
    	}
    	final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
    			notificationUid, channelId, false /* includeDeleted */);
    	if (channel == null) {
    		final String noChannelStr = "No Channel found for "
    				+ "pkg=" + pkg
    				+ ", channelId=" + channelId
    				+ ", id=" + id
    				+ ", tag=" + tag
    				+ ", opPkg=" + opPkg
    				+ ", callingUid=" + callingUid
    				+ ", userId=" + userId
    				+ ", incomingUserId=" + incomingUserId
    				+ ", notificationUid=" + notificationUid
    				+ ", notification=" + notification;
    		Log.e(TAG, noChannelStr);
    		doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
    				"Failed to post notification on channel \"" + channelId + "\"\n" +
    				"See log for more details");
    		return;
    	}
    
    	final StatusBarNotification n = new StatusBarNotification(
    			pkg, opPkg, id, tag, notificationUid, callingPid, notification,
    			user, null, System.currentTimeMillis());
    	final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
    
    	if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
    			&& (channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
    			&& (r.getImportance() == IMPORTANCE_MIN || r.getImportance() == IMPORTANCE_NONE)) {
    		// Increase the importance of foreground service notifications unless the user had an
    		// opinion otherwise
    		if (TextUtils.isEmpty(channelId)
    				|| NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
    			r.setImportance(IMPORTANCE_LOW, "Bumped for foreground service");
    		} else {
    			channel.setImportance(IMPORTANCE_LOW);
    			mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
    			r.updateNotificationChannel(channel);
    		}
    	}
    
    	if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
    			r.sbn.getOverrideGroupKey() != null)) {
    		return;
    	}
    
    	// Whitelist pending intents.
    	if (notification.allPendingIntents != null) {
    		final int intentCount = notification.allPendingIntents.size();
    		if (intentCount > 0) {
    			final ActivityManagerInternal am = LocalServices
    					.getService(ActivityManagerInternal.class);
    			final long duration = LocalServices.getService(
    					DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();
    			for (int i = 0; i < intentCount; i++) {
    				PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
    				if (pendingIntent != null) {
    					am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(),
    							WHITELIST_TOKEN, duration);
    				}
    			}
    		}
    	}
    
    	mHandler.post(new EnqueueNotificationRunnable(userId, r));
    }
    

    这个函数虽然很长,但是只要静下心来看一下,会发现有用的没几行(其实只有4行)。

    if (DBG) {
    	Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
    			+ " notification=" + notification);
    }
    

    函数最开头的这4行,给了我们重要的信息:包名。包名是直接从NotificationManager中传过来的,所以我们可以在这里对包名进行过滤,发现是被禁用掉的包名就直接返回。这个函数的返回值类型是void,直接返回也不会有其他的影响。
    我们把过滤的逻辑就加在这个打印日志行的下面,实际上,就是在NotificationManagerService在处理应用发送通知的一开头就进行过滤,这样对其内部逻辑的影响最小,避免Service内部的状态被我们加的代码搞乱。

    好的,改到这里,被禁用的APP就不能发送新的通知了。那么,在被禁用之前发送的通知怎么办呢?用户点击之后还是可以进入啊?所以,在通过ContentProvider监听到禁用APP的名单发生变化之后,要在onChange函数里面,把已经用的APP的所有通知全部清除掉。下面我们来看看NotificationManager中的清除通知的函数。

    /**
     * Cancel a previously shown notification.  If it's transient, the view
     * will be hidden.  If it's persistent, it will be removed from the status
     * bar.
     */
    public void cancel(int id)
    {
        cancel(null, id);
    }
    
    /**
     * Cancel a previously shown notification.  If it's transient, the view
     * will be hidden.  If it's persistent, it will be removed from the status
     * bar.
     */
    public void cancel(String tag, int id)
    {
        cancelAsUser(tag, id, new UserHandle(UserHandle.myUserId()));
    }
    
    /**
     * @hide
     */
    public void cancelAsUser(String tag, int id, UserHandle user)
    {
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
        try {
            service.cancelNotificationWithTag(pkg, tag, id, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    
    /**
     * Cancel all previously shown notifications. See {@link #cancel} for the
     * detailed behavior.
     */
    public void cancelAll()
    {
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        if (localLOGV) Log.v(TAG, pkg + ": cancelAll()");
        try {
            service.cancelAllNotifications(pkg, UserHandle.myUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    

    前2个函数最终都调用到了cancelAsUser,它和cancelAll的区别就是后者会把当前应用发的所有通知都清除掉。所以,看这个就好了。这里调用到了NotificationManagerService的cancelAllNotifications函数。

    @Override
    public void cancelAllNotifications(String pkg, int userId) {
        checkCallerIsSystemOrSameApp(pkg);
    
        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
    
        // Calling from user space, don't allow the canceling of actively
        // running foreground services.
        cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
                pkg, null, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,
                REASON_APP_CANCEL_ALL, null);
    }
    

    这个函数就是通过cancelAllNotificationsInt这个函数,清除掉指定包名的所有通知。因为这个函数时实现的INotificationManager.Stub,所以,我们在NotificationObserver里面调用不到,所以,我们仿照这个函数的写法,调用cancelAllNotificationsInt函数。我们看一下cancelAllNotificationsInt函数的定义:

    **
     * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
     * and none of the {@code mustNotHaveFlags}.
     */
    void cancelNotification(final int callingUid, final int callingPid,
            final String pkg, final String tag, final int id,
            final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
            final int userId, final int reason, final ManagedServiceInfo listener) {
            ...
            }
    

    这个函数里面有两个flag:mustHaveFlags和mustNotHaveFlags。从函数注释看出,这个函数清除的通知必须包含所有的mustHaveFlags,同时mustNotHaveFlag必须一个都没有。那么,对我们来说,我们要清除的是指定应用的所有通知,不要这么多的限制条件。所以,这两个flag传0即可。

    cancelAllNotificationsInt(Binder.getCallingUid(),
                                Binder.getCallingPid(),
                                pkg,
                                null,
                                0,
                                0,
                                true,
                                UserHandle.myUserId(),
                                REASON_APP_CANCEL_ALL,
                                null);
    
    展开全文
  • 上篇文章最后留了一个问题:就是如果禁用app的时刻,被禁用的APP是打开的或者是最近应用视图正在显示,该怎么处理? 我的解决方案是模拟一个HOME键的事件。这样,就能退出的状态,回到主页,同时给出提示,告知用户...

    上篇文章最后留了一个问题:就是如果禁用app的时刻,被禁用的APP是打开的或者是最近应用视图正在显示,该怎么处理?
    我的解决方案是模拟一个HOME键的事件。这样,就能退出的状态,回到主页,同时给出提示,告知用户APP已被禁用。

    这里就有两个问题了:1、如何模拟HOME按键。2、如何判断哪个APP在前台。

    模拟按键

    我们知道,用adb可以模拟按键,那么,我们就从input命令的实现入手。input的实现是在framework/base/cmds/input/src/com/android/commands/input/input.java。

    public static void main(String[] args) {
    	(new Input()).run(args);
    }
    
    private void run(String[] args) {
    
    	...
    
    	try {
    		if (command.equals("text")) {
    			
    			...
    			
    		} else if (command.equals("keyevent")) {
    			if (length >= 2) {
    				final boolean longpress = "--longpress".equals(args[index + 1]);
    				final int start = longpress ? index + 2 : index + 1;
    				inputSource = getSource(inputSource, InputDevice.SOURCE_KEYBOARD);
    				if (length > start) {
    					for (int i = start; i < length; i++) {
    						int keyCode = KeyEvent.keyCodeFromString(args[i]);
    						if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
    							keyCode = KeyEvent.keyCodeFromString("KEYCODE_" + args[i]);
    						}
    						sendKeyEvent(inputSource, keyCode, longpress);
    					}
    					return;
    				}
    			}
    		}
    		
    		...
    		
    	} catch (NumberFormatException ex) {
    	}
    
    	...
    	
    }
    

    这里,通过getSource()函数获取inputSource,然后获取int类型的keyCode,再调用sendKeyEvent函数:

    private void sendKeyEvent(int inputSource, int keyCode, boolean longpress) {
        long now = SystemClock.uptimeMillis();
        injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
        if (longpress) {
            injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 1, 0,
                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_LONG_PRESS,
                    inputSource));
        }
        injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
    }
    

    这里调用injectKeyEvent()函数生成按下、长按和抬起事件:

    private void injectKeyEvent(KeyEvent event) {
        Log.i(TAG, "injectKeyEvent: " + event);
        InputManager.getInstance().injectInputEvent(event,
                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
    }
    

    这里就是调用InputManager的injectInputEvent方法插入相应的事件了。所以,我们在APP里面也可以这么做。

    InputManager中的injectInputEvent方法是hide的,所以想要在APP里调用,要么用反射,要么就用定制的android.jar。这里我使用反射来调用。

    sendKeyEvent(InputDevice.SOURCE_KEYBOARD, KeyEvent.KEYCODE_HOME, false);
    
    private void sendKeyEvent(int inputSource, int keyCode, boolean longpress) {
        long now = SystemClock.uptimeMillis();
        injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
        if (longpress) {
            injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 1, 0,
                    KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_LONG_PRESS,
                    inputSource));
        }
        injectKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource));
    }
    
    private void injectKeyEvent(KeyEvent event) {
        Log.i(TAG, "injectKeyEvent: " + event);
        InputManager im = (InputManager) getSystemService(Context.INPUT_SERVICE);
        Method method;
        try {
            method = InputManager.class.getMethod("injectInputEvent", InputEvent.class, int.class);
            method.invoke(im, event, 2);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
    }
    

    据说,模拟HOME键必须是系统应用,也就是声明uid为system,且有system签名的应用。

    判断前台应用

    网上有很多判断应用是否在前台的方法,都不是很直接,不太符合我的需求。我这里通过在ActivityManagerService中增加代码,以提供给APP前台应用的信息。

    以前我做过通过在Instrumention这个类里面的callActivityOnResume方法里面增加广播,来通知APP前台应用有切换。但是在我们的这个需求里面,如果用广播的方式,那么APP就无法做查询,只能是在收到广播之后自己记录当前的前台应用。这么做不是很优雅。如果在Instrumention里面写系统属性或者Settings里面的值,因为Instrumention里面的两个context并不是system,会导致经常无法写值。

    在ActivityManagerService中搜索resume,我找到了这几个方法:

    @Override
    public final void activityResumed(IBinder token) {
        final long origId = Binder.clearCallingIdentity();
        synchronized(this) {
            ActivityRecord.activityResumedLocked(token);
            mWindowManager.notifyAppResumedFinished(token);
        }
        Binder.restoreCallingIdentity(origId);
    }
    
    @Override
    public final void activityPaused(IBinder token) {
        final long origId = Binder.clearCallingIdentity();
        synchronized(this) {
            ActivityStack stack = ActivityRecord.getStackLocked(token);
            if (stack != null) {
                stack.activityPausedLocked(token, false);
            }
        }
        Binder.restoreCallingIdentity(origId);
    }
    
    @Override
    public final void activityStopped(IBinder token, Bundle icicle,
            PersistableBundle persistentState, CharSequence description) {
        if (DEBUG_ALL) Slog.v(TAG, "Activity stopped: token=" + token);
    
        // Refuse possible leaked file descriptors
        if (icicle != null && icicle.hasFileDescriptors()) {
            throw new IllegalArgumentException("File descriptors passed in Bundle");
        }
    
        final long origId = Binder.clearCallingIdentity();
    
        synchronized (this) {
            final ActivityRecord r = ActivityRecord.isInStackLocked(token);
            if (r != null) {
                r.activityStoppedLocked(icicle, persistentState, description);
            }
        }
    
        trimApplications();
    
        Binder.restoreCallingIdentity(origId);
    }
    
    @Override
    public final void activityDestroyed(IBinder token) {
        if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token);
        synchronized (this) {
            ActivityStack stack = ActivityRecord.getStackLocked(token);
            if (stack != null) {
                stack.activityDestroyedLocked(token, "activityDestroyed");
            }
        }
    }
    
    @Override
    public final void activityRelaunched(IBinder token) {
        final long origId = Binder.clearCallingIdentity();
        synchronized (this) {
            mStackSupervisor.activityRelaunchedLocked(token);
        }
        Binder.restoreCallingIdentity(origId);
    }
    

    从函数名来看,这几个函数应该与Activity声明周期对应的,所以,我在这里增加日志跑了一下,发现确实如此。而且,Service里面的context是system的,能用来写属性和Settings。那么,我们就在activityResumed()方法里面增加写系统属性或者Settings的代码。

    但是,当我写Settings的时候,报了context为空。所以,最终我写的是系统属性。

    activityResumed()方法的参数类型是IBinder,怎么通过IBinder获取到包名呢?在ActivityManagerService里面搜索一下是package,就找到了下面两个方法:

    @Override
    public ComponentName getActivityClassForToken(IBinder token) {
        synchronized(this) {
            ActivityRecord r = ActivityRecord.isInStackLocked(token);
            if (r == null) {
                return null;
            }
            return r.intent.getComponent();
        }
    }
    
    @Override
    public String getPackageForToken(IBinder token) {
        synchronized(this) {
            ActivityRecord r = ActivityRecord.isInStackLocked(token);
            if (r == null) {
                return null;
            }
            return r.packageName;
        }
    }
    

    通过这两个方法,不光能获取到包名,连Activity的名字都能获取到。

    展开全文
  • 以下是接收广播的程序代码:package ...import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;public class BootBroadcastReceiver extends Broa...
  • 支付宝内搜索 9155838 即可领现金红包 每天都能领哦16:33:33现在...占用手机资源让人很不爽,在这里提供一些禁用掉这些应用的方法供参考:先看通过adb停用安卓系统应用的效果ADB调试工具包(Android Debug Bridge)输...
  • /*** 使用代码消除App数据* 我们不寻常的清除App数据,中找到相应的App* 然后选择其清除数据.以下给出代码实现.** 注意事项:* 1 设备须要root* 2 该演示样例中删除的是系统级应用* 2 注意在命令的末尾须要加上换行\n*...
  • android app权限_如何限制Android App权限

    千次阅读 2020-09-16 13:21:40
    android app权限Android forces you to agree to every permission an app wants, assuming you want to use the app. After rooting your device, you can manage permissions on a per-app basis. 如果您要使用该...
  • Android禁用第三方应用

    2021-06-03 09:32:52
    需要权限android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE而这个权限是只有system app才能使用,所以app需要系统签名。非system app即便在Android Mainfest.xml中强制写上,安装时也部会写入/data/...
  • android 定制化开发中,可以有需求要实现禁止app启动和允许app运行的接口,禁用app后已安装的应用从桌面消失,只存在于系统设置内的应用列表里,无法调用。启用后,恢复正常使用,在桌面显示。对于app管理的都是由...
  • android手机停用或卸载自带app方法

    千次阅读 2020-08-03 15:37:30
    在手机上调出开发者模式: ...系统版本号 如: 再到系统中可以看到开发人员先项,进入该先项,如: ...adb shell pm list packages -d //只输出禁用的包 adb shell pm list packages -e //只输出...
  • AndroidApp中强制关闭禁用深色模式

    千次阅读 2021-01-20 17:37:22
    item name="android:forceDarkAllowed" tools:targetApi="q">false</item> 2.完整的代码 <?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools=...
  • 使用系统是android5.1 根据客户需求修改的 禁用usb鼠标和键盘!客户的需求是在设置选项里面添加两个选项可以分别禁用usb鼠标 和 键盘 已经修改完成亲测!有完整的步骤和代码! 根据代码小修改一下就可以变成 随时...
  • Android 禁用软键盘

    2021-06-04 01:56:43
    import android.app.Activity;import android.content.Context 我们在Android提供的EditText中单击的时候,会自动的弹出软键盘,其实对于软键盘的控制我们可以通过InputMethodManager这个类来实现。 强制关闭软键盘/...
  • 上一个文章记录了 如何解决使用Hbuilderx打包Vue项目为安卓APP点击物理返回键直接退出应用问题,这篇文章记录一下让某个页面禁用物理返回按钮的问题 问题描述 上次解决了返回按钮,但是在登录成功之后还是可以返回到...
  • 可以看出Android不让其他app禁用另外的app。 想让你在同一个app里操作这个应用的禁用,所以会根据你的uid,判断是不是一个app在做这个操作。  因此,可以看出,系统想让自身来管理自己,所以最好是在自己app...
  • 禁用Android系统Home键

    2015-04-08 21:07:28
    此代码来自于https://github.com/shaobin0604/Android-HomeKey-Locker。目测有效文件存在于HomeLockerLib和HomeLockerSample文件夹。
  • 在进行网络模块开发中,根据客户要求设置某些app可以上网,某些app不可以上网,就是所谓的网络白名单功能 系统整个网络模块都是由NMS服务负责通讯的 接下来先看下NetworkManagementService.java @Override public ...
  • activity android:name=".music_play_Activity" android:screenOrientation="portrait"/> 我们只要给Activity的screenOrientation属性设置就好了: android:screenOrientation=”portrait” 始终竖屏 android:...
  • Touch Protecto是一款安卓平台上用来临时禁用禁用手机触摸屏软件,意思就是开启App之后你即便疯狂触摸屏幕也不会产生任何交互,以防止某些特定场景的意外操作触摸屏,从此达到防误触的目的。Touch Protector虽然是...
  • 现在就来实现在多任务菜单中实现APP禁用。 入口 Framework中对各种按键的处理都是在PhoneWIndowManager中,这里会通过几个intercept函数,对Home、Back、媒体按键等进行拦截,不让这几个按键的键值传递给APP。我们...
  • /** * 注意: * super.onBackPressed()会自动调用finish()方法,关闭当前Activity. */ @Override public void onBackPressed() { super.onBackPressed(); Toast.makeText(this, "按下了back键 ...
  • * check the app is installed */ public static boolean isAppInstalled(String packageName) { PackageInfo packageInfo; ApplicationInfo applicationInfo = null; try { packageInfo...
  • android app 禁用statusbar 功能

    千次阅读 2017-08-23 11:30:15
    最近被要求研究下怎么在app层面禁止android的statusbar功能,查阅了资料以及系统的statusbar源码,发现真正的禁用statusbar的功能,基本都是系统应用调用statusBarManager 的disable方法,但是要调用到这个方法需要...
  • android禁用其他应用By default, Android apps downloaded from the Google Play Store are automatically updated. If you would rather review the updates and any new or changed permissions associated with ...
  • android APP如何实现launcher

    千次阅读 2016-08-01 16:10:38
    将自己做的APP做成android的launcher,开机后不再进入系统的桌面UI,而是直接进入APP。 首先是 AndroidManifest.xml 里面: 里面添加这两个属性: android:largeHeap= "true" android:persistent= "true...
  • 广播接收器是Android应用程序的基本组成部分之一。 在Android应用程序中添加广播接收器有两种不同的方法。 可以以编程方式或在Android Manifest文件中添加它。... 有一种方法可以启用和禁用清单文件中添加的广...
  • 加入您的layout.xmlandroid:descendantFocusability="blocksDescendants"希望按照主(父)布局代码它会工作。并设置以下属性在你的WebViewandroid:focusable="false"android:focusableInTouchMode="true"See it:xmlns:...
  • app需要进入淘宝优惠卷界面(H5)领卷购买商品,但这个H5界面会自动唤起手机淘宝经检查scheme为tbopen://,我想问下怎样禁止唤起手淘?
  • 我终于找到了解决方案.显然并非所有手机都有此选项:Home > Menu > Settings > Wireless & networks > Mobile network (checkbox)但是,对于那些做的人,这种方法将起作用:/*** @return null if ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,150
精华内容 10,460
关键字:

安卓禁用app