/* * Copyright 2015. Appsi Mobile * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.appsimobile.appsii.compat; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings; import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.List; /** * Version of {@link LauncherAppsCompat} for devices with API level 16. * Devices Pre-L don't support multiple profiles in one launcher so * user parameters are ignored and all methods operate on the current user. */ public class LauncherAppsCompatV16 extends LauncherAppsCompat { private final PackageManager mPm; private final Context mContext; private final List<OnAppsChangedCallbackCompat> mCallbacks = new ArrayList<OnAppsChangedCallbackCompat>(); private final PackageMonitor mPackageMonitor; public LauncherAppsCompatV16(Context context) { mPm = context.getPackageManager(); mContext = context; mPackageMonitor = new PackageMonitor(); } @Nullable public List<LauncherActivityInfoCompat> getActivityList(String packageName, UserHandleCompat user) { final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); mainIntent.setPackage(packageName); List<ResolveInfo> infos = safelyGetResolveInfos(mainIntent); if (infos == null) return null; List<LauncherActivityInfoCompat> list = new ArrayList<LauncherActivityInfoCompat>(infos.size()); for (ResolveInfo info : infos) { list.add(new LauncherActivityInfoCompatV16(mContext, info)); } return list; } @Nullable private List<ResolveInfo> safelyGetResolveInfos(Intent mainIntent) { for (int i = 0; i < 5; i++) { try { return mPm.queryIntentActivities(mainIntent, 0); } catch (RuntimeException e) { try { Thread.sleep(500); } catch (InterruptedException ignore) { } } } return null; } public LauncherActivityInfoCompat resolveActivity(Intent intent, UserHandleCompat user) { ResolveInfo info = mPm.resolveActivity(intent, 0); if (info != null) { return new LauncherActivityInfoCompatV16(mContext, info); } return null; } public void startActivityForProfile(ComponentName component, UserHandleCompat user, Rect sourceBounds, Bundle opts) { Intent launchIntent = new Intent(Intent.ACTION_MAIN); launchIntent.addCategory(Intent.CATEGORY_LAUNCHER); launchIntent.setComponent(component); launchIntent.setSourceBounds(sourceBounds); launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(launchIntent, opts); } public void showAppDetailsForProfile(ComponentName component, UserHandleCompat user) { String packageName = component.getPackageName(); Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", packageName, null)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); mContext.startActivity(intent, null); } public synchronized void addOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) { if (callback != null && !mCallbacks.contains(callback)) { mCallbacks.add(callback); if (mCallbacks.size() == 1) { registerForPackageIntents(); } } } public synchronized void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) { mCallbacks.remove(callback); if (mCallbacks.size() == 0) { unregisterForPackageIntents(); } } public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) { try { PackageInfo info = mPm.getPackageInfo(packageName, 0); return info != null && info.applicationInfo.enabled; } catch (NameNotFoundException e) { return false; } } public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) { try { ActivityInfo info = mPm.getActivityInfo(component, 0); return info != null && info.isEnabled(); } catch (NameNotFoundException e) { return false; } } private void unregisterForPackageIntents() { mContext.unregisterReceiver(mPackageMonitor); } private void registerForPackageIntents() { IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); mContext.registerReceiver(mPackageMonitor, filter); filter = new IntentFilter(); filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); mContext.registerReceiver(mPackageMonitor, filter); } synchronized List<OnAppsChangedCallbackCompat> getCallbacks() { return new ArrayList<>(mCallbacks); } private class PackageMonitor extends BroadcastReceiver { PackageMonitor() { } public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); final UserHandleCompat user = UserHandleCompat.myUserHandle(); if (Intent.ACTION_PACKAGE_CHANGED.equals(action) || Intent.ACTION_PACKAGE_REMOVED.equals(action) || Intent.ACTION_PACKAGE_ADDED.equals(action)) { final String packageName = intent.getData().getSchemeSpecificPart(); final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); if (packageName == null || packageName.length() == 0) { // they sent us a bad intent return; } if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { for (OnAppsChangedCallbackCompat callback : getCallbacks()) { callback.onPackageChanged(packageName, user); } } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { if (!replacing) { for (OnAppsChangedCallbackCompat callback : getCallbacks()) { callback.onPackageRemoved(packageName, user); } } // else, we are replacing the package, so a PACKAGE_ADDED will be sent // later, we will update the package at this time } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { if (!replacing) { for (OnAppsChangedCallbackCompat callback : getCallbacks()) { callback.onPackageAdded(packageName, user); } } else { for (OnAppsChangedCallbackCompat callback : getCallbacks()) { callback.onPackageChanged(packageName, user); } } } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { // EXTRA_REPLACING is available Kitkat onwards. For lower devices, it is broadcasted // when moving a package or mounting/un-mounting external storage. Assume that // it is a replacing operation. final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT); String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); for (OnAppsChangedCallbackCompat callback : getCallbacks()) { callback.onPackagesAvailable(packages, user, replacing); } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT); String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); for (OnAppsChangedCallbackCompat callback : getCallbacks()) { callback.onPackagesUnavailable(packages, user, replacing); } } } } }