Java tutorial
/* * IntentsLab - Android app for playing with Intents and Binder IPC * Copyright (C) 2014 Micha Bednarski * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package com.github.michalbednarski.intentslab.browser; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.os.Build; import android.os.Parcel; import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.Spinner; import com.github.michalbednarski.intentslab.R; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; /** * Fetcher for application components */ public class ComponentFetcher extends Fetcher { private static final String TAG = "ComponentFetcher"; static final boolean DEVELOPMENT_PERMISSIONS_SUPPORTED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; /** * Type of components to get * * Combination of following flags: * <ul> * <li> {@link PackageManager#GET_ACTIVITIES} * <li> {@link PackageManager#GET_RECEIVERS} * <li> {@link PackageManager#GET_SERVICES} * <li> {@link PackageManager#GET_PROVIDERS} * </ul> */ public int type = PackageManager.GET_ACTIVITIES; public static final int APP_TYPE_USER = 1; public static final int APP_TYPE_SYSTEM = 2; public int appType = APP_TYPE_USER; public static final int PROTECTION_WORLD_ACCESSIBLE = 1; public static final int PROTECTION_NORMAL = 2; public static final int PROTECTION_DANGEROUS = 4; public static final int PROTECTION_SIGNATURE = 8; public static final int PROTECTION_SYSTEM = 16; public static final int PROTECTION_DEVELOPMENT = 32; public static final int PROTECTION_UNEXPORTED = 64; public static final int PROTECTION_UNKNOWN = 128; public int protection = PROTECTION_WORLD_ACCESSIBLE; public static final int PROTECTION_ANY = 128 * 2 - 1; public static final int PROTECTION_ANY_OBTAINABLE = PROTECTION_WORLD_ACCESSIBLE | PROTECTION_NORMAL | PROTECTION_DANGEROUS | PROTECTION_DEVELOPMENT; public static final int PROTECTION_ANY_EXPORTED = PROTECTION_ANY & ~PROTECTION_UNEXPORTED; static final int PROTECTION_ANY_LEVEL = ComponentFetcher.PROTECTION_NORMAL | ComponentFetcher.PROTECTION_DANGEROUS | ComponentFetcher.PROTECTION_SIGNATURE | ComponentFetcher.PROTECTION_SYSTEM | ComponentFetcher.PROTECTION_DEVELOPMENT; /** * Preset protection filters for displaying in Spinner in dialog */ private static final int[] PROTECTION_PRESETS = new int[] { PROTECTION_ANY, PROTECTION_ANY_EXPORTED, PROTECTION_ANY_OBTAINABLE, PROTECTION_WORLD_ACCESSIBLE }; /** * Preset protection filters for displaying in Spinner in dialog */ private static final int[] PROTECTION_PRESETS_MENU_IDS = new int[] { R.id.permission_filter_all, R.id.permission_filter_exported, R.id.permission_filter_obtainable, R.id.permission_filter_world_accessible }; /** * This is used for checking if we can skip looking up PermissionInfo because filtering result will be the same * no matter what protectionLevel is set */ private static final int PROTECTION_ANY_PERMISSION = PROTECTION_ANY & ~(PROTECTION_WORLD_ACCESSIBLE | PROTECTION_UNEXPORTED); public String requireMetaDataSubstring = null; public boolean testWritePermissionForProviders = false; public boolean includeOnlyProvidersAllowingPermissionGranting = false; public ComponentFetcher() { } // Fetching @Override Object getEntries(Context context) { PackageManager pm = context.getPackageManager(); int requestedPackageInfoFlags = type | PackageManager.GET_DISABLED_COMPONENTS | (requireMetaDataSubstring != null ? PackageManager.GET_META_DATA : 0); boolean workAroundSmallBinderBuffer = false; List<PackageInfo> allPackages = null; try { allPackages = pm.getInstalledPackages(requestedPackageInfoFlags); } catch (Exception e) { Log.w(TAG, "Loading all apps at once failed, retrying separately", e); } if (allPackages == null || allPackages.isEmpty()) { workAroundSmallBinderBuffer = true; allPackages = pm.getInstalledPackages(0); } ArrayList<Category> selectedApps = new ArrayList<Category>(); for (PackageInfo pack : allPackages) { // Filter out non-applications if (pack.applicationInfo == null) { continue; } // System app filter if ((((pack.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? APP_TYPE_SYSTEM : APP_TYPE_USER) & appType) == 0) { continue; } // Load component information separately if they were to big to send them all at once if (workAroundSmallBinderBuffer) { try { pack = pm.getPackageInfo(pack.packageName, requestedPackageInfoFlags); } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "getPackageInfo() thrown NameNotFoundException for " + pack.packageName, e); continue; } } // Scan components ArrayList<Component> selectedComponents = new ArrayList<Component>(); if ((type & PackageManager.GET_ACTIVITIES) != 0) { scanComponents(pm, pack.activities, selectedComponents); } if ((type & PackageManager.GET_RECEIVERS) != 0) { scanComponents(pm, pack.receivers, selectedComponents); } if ((type & PackageManager.GET_SERVICES) != 0) { scanComponents(pm, pack.services, selectedComponents); } if ((type & PackageManager.GET_PROVIDERS) != 0) { scanComponents(pm, pack.providers, selectedComponents); } // Check if we filtered out all components and skip whole app if so if (selectedComponents.isEmpty()) { continue; } // Build and add app descriptor Category app = new Category(); app.title = String.valueOf(pack.applicationInfo.loadLabel(pm)); app.subtitle = pack.packageName; app.components = selectedComponents.toArray(new Component[selectedComponents.size()]); selectedApps.add(app); // Allow cancelling task if (Thread.interrupted()) { return null; } } return selectedApps.toArray(new Category[selectedApps.size()]); } private void scanComponents(PackageManager pm, ComponentInfo[] components, ArrayList<Component> outList) { // Skip apps not having any components of requested type if (!(components != null && components.length != 0)) { return; } // Scan components for (ComponentInfo cmp : components) { if (!checkMetaDataFilter(cmp)) { continue; } if (!checkPermissionFilter(pm, cmp)) { continue; } if (includeOnlyProvidersAllowingPermissionGranting && cmp instanceof ProviderInfo) { ProviderInfo providerInfo = (ProviderInfo) cmp; if (!providerInfo.grantUriPermissions) { continue; } } Component component = new Component(); String name = cmp.name; String packageName = cmp.packageName; component.title = name.startsWith(packageName) ? name.substring(packageName.length()) : name; component.componentInfo = cmp; outList.add(component); } } private boolean checkMetaDataFilter(ComponentInfo cmp) { if (requireMetaDataSubstring == null) { return true; } if (cmp.metaData == null || cmp.metaData.isEmpty()) { return false; } if (requireMetaDataSubstring.length() == 0) { return true; } for (String key : cmp.metaData.keySet()) { if (key.contains(requireMetaDataSubstring)) { return true; } } return false; } private boolean checkPermissionFilter(PackageManager pm, ComponentInfo cmp) { // Not exported? if (!cmp.exported) { return (protection & PROTECTION_UNEXPORTED) != 0; } // Get checked permission String permission = cmp instanceof ServiceInfo ? ((ServiceInfo) cmp).permission : cmp instanceof ActivityInfo ? ((ActivityInfo) cmp).permission : cmp instanceof ProviderInfo ? (testWritePermissionForProviders ? ((ProviderInfo) cmp).writePermission : ((ProviderInfo) cmp).readPermission) : null; // World accessible if (permission == null) { return (protection & PROTECTION_WORLD_ACCESSIBLE) != 0; } // Skip checking protectionLevel if it doesn't matter if ((protection & PROTECTION_ANY_PERMISSION) == PROTECTION_ANY_PERMISSION) { return true; } if ((protection & PROTECTION_ANY_PERMISSION) == 0) { return false; } // Obtain PermissionInfo PermissionInfo permissionInfo; try { permissionInfo = pm.getPermissionInfo(permission, 0); } catch (PackageManager.NameNotFoundException e) { Log.v("PermissionFilter", "Unknown permission " + permission + " for " + cmp.name, e); return (protection & PROTECTION_UNKNOWN) != 0; } return checkProtectionLevel(permissionInfo, protection); } @SuppressLint("InlinedApi") static boolean checkProtectionLevel(PermissionInfo permissionInfo, int protectionFilter) { // Skip test if all options are checked if ((protectionFilter & PROTECTION_ANY_LEVEL) == PROTECTION_ANY_LEVEL) { return true; } // Test protectionLevel int protectionLevel = permissionInfo.protectionLevel; if (protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { protectionLevel = PermissionInfo.PROTECTION_SIGNATURE | PermissionInfo.PROTECTION_FLAG_SYSTEM; } int protectionLevelBase = protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; int protectionLevelFlags = protectionLevel & PermissionInfo.PROTECTION_MASK_FLAGS; // Match against our flags return ((protectionLevel == PermissionInfo.PROTECTION_NORMAL ? PROTECTION_NORMAL : protectionLevel == PermissionInfo.PROTECTION_DANGEROUS ? PROTECTION_DANGEROUS : (((protectionLevelBase == PermissionInfo.PROTECTION_SIGNATURE) ? PROTECTION_SIGNATURE : 0) | (((protectionLevelFlags & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) ? PROTECTION_SYSTEM : 0) | (((protectionLevelFlags & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) ? PROTECTION_DEVELOPMENT : 0))) & protectionFilter) != 0; } // Configuration UI @Override int getConfigurationLayout() { return R.layout.components_filter; } @Override void initConfigurationForm(final FetcherOptionsDialog dialog) { // Disable development permission checkbox if it's not available if (!DEVELOPMENT_PERMISSIONS_SUPPORTED) { dialog.findView(R.id.permission_filter_development).setEnabled(false); } // Prepare protection preset spinner { // Find current preset int currentPresetId = PROTECTION_PRESETS.length; // "Custom" if nothing found if (!testWritePermissionForProviders) { for (int i = 0; i < PROTECTION_PRESETS.length; i++) { if (protection == PROTECTION_PRESETS[i]) { currentPresetId = i; dialog.findView(R.id.permission_filter_details).setVisibility(View.GONE); break; } } } // Fill spinner Spinner protectionPresetSpinner = (Spinner) dialog.findView(R.id.permission_filter_spinner); Activity activity = dialog.getActivity(); protectionPresetSpinner .setAdapter(new ArrayAdapter<String>(activity, android.R.layout.simple_spinner_item, new String[] { activity.getString(R.string.permission_filter_show_all), // 0 activity.getString(R.string.permission_filter_show_exported), // 1 activity.getString(R.string.permission_filter_show_obtainable), // 2 activity.getString(R.string.permission_filter_world_accessible), // 3 activity.getString(R.string.filter_custom) // 4 })); protectionPresetSpinner.setSelection(currentPresetId); // Set up spinner event protectionPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { boolean isCustom = position == PROTECTION_PRESETS.length; if (!isCustom) { int preset = PROTECTION_PRESETS[position]; dialog.setBoxChecked(R.id.permission_filter_world_accessible, (preset & PROTECTION_WORLD_ACCESSIBLE) != 0); dialog.setBoxChecked(R.id.permission_filter_normal, (preset & PROTECTION_NORMAL) != 0); dialog.setBoxChecked(R.id.permission_filter_dangerous, (preset & PROTECTION_DANGEROUS) != 0); dialog.setBoxChecked(R.id.permission_filter_signature, (preset & PROTECTION_SIGNATURE) != 0); dialog.setBoxChecked(R.id.permission_filter_system, (preset & PROTECTION_SYSTEM) != 0); dialog.setBoxChecked(R.id.permission_filter_development, (preset & PROTECTION_DEVELOPMENT) != 0); dialog.setBoxChecked(R.id.permission_filter_unexported, (preset & PROTECTION_UNEXPORTED) != 0); dialog.setBoxChecked(R.id.permission_filter_unknown, (preset & PROTECTION_UNKNOWN) != 0); dialog.setBoxChecked(R.id.read_permission, true); } dialog.findView(R.id.permission_filter_details) .setVisibility(isCustom ? View.VISIBLE : View.GONE); } @Override public void onNothingSelected(AdapterView<?> parent) { // Spinner cannot have nothing selected } }); } // Fill form dialog.setBoxChecked(R.id.system_apps, (appType & APP_TYPE_SYSTEM) != 0); dialog.setBoxChecked(R.id.user_apps, (appType & APP_TYPE_USER) != 0); dialog.setBoxChecked(R.id.activities, (type & PackageManager.GET_ACTIVITIES) != 0); dialog.setBoxChecked(R.id.receivers, (type & PackageManager.GET_RECEIVERS) != 0); dialog.setBoxChecked(R.id.services, (type & PackageManager.GET_SERVICES) != 0); dialog.setBoxChecked(R.id.content_providers, (type & PackageManager.GET_PROVIDERS) != 0); dialog.setBoxChecked(R.id.permission_filter_world_accessible, (protection & PROTECTION_WORLD_ACCESSIBLE) != 0); dialog.setBoxChecked(R.id.permission_filter_normal, (protection & PROTECTION_NORMAL) != 0); dialog.setBoxChecked(R.id.permission_filter_dangerous, (protection & PROTECTION_DANGEROUS) != 0); dialog.setBoxChecked(R.id.permission_filter_signature, (protection & PROTECTION_SIGNATURE) != 0); dialog.setBoxChecked(R.id.permission_filter_system, (protection & PROTECTION_SYSTEM) != 0); dialog.setBoxChecked(R.id.permission_filter_development, (protection & PROTECTION_DEVELOPMENT) != 0); dialog.setBoxChecked(R.id.permission_filter_unexported, (protection & PROTECTION_UNEXPORTED) != 0); dialog.setBoxChecked(R.id.permission_filter_unknown, (protection & PROTECTION_UNKNOWN) != 0); dialog.setBoxChecked(testWritePermissionForProviders ? R.id.write_permission : R.id.read_permission, true); dialog.setBoxChecked(R.id.only_providers_with_grant_uri_permission, includeOnlyProvidersAllowingPermissionGranting); dialog.setBoxChecked(R.id.metadata, requireMetaDataSubstring != null); dialog.setTextInField(R.id.metadata_substring, requireMetaDataSubstring); // Set up sections showing when their checkboxes are checked dialog.findView(R.id.content_provider_permission_type) .setVisibility(testWritePermissionForProviders ? View.VISIBLE : View.GONE); dialog.findView(R.id.content_provider_options) .setVisibility((type & PackageManager.GET_PROVIDERS) != 0 ? View.VISIBLE : View.GONE); ((CheckBox) dialog.findView(R.id.content_providers)) .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { dialog.findView(R.id.content_provider_permission_type) .setVisibility(isChecked ? View.VISIBLE : View.GONE); dialog.findView(R.id.content_provider_options) .setVisibility(isChecked ? View.VISIBLE : View.GONE); if (!isChecked) { dialog.setBoxChecked(R.id.read_permission, true); dialog.setBoxChecked(R.id.only_providers_with_grant_uri_permission, false); } } }); dialog.findView(R.id.metadata_details) .setVisibility(requireMetaDataSubstring != null ? View.VISIBLE : View.GONE); ((CheckBox) dialog.findView(R.id.metadata)) .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { dialog.findView(R.id.metadata_details).setVisibility(isChecked ? View.VISIBLE : View.GONE); if (!isChecked) { dialog.setTextInField(R.id.metadata_substring, ""); } } }); } @Override void updateFromConfigurationForm(FetcherOptionsDialog dialog) { appType = (dialog.isBoxChecked(R.id.system_apps) ? APP_TYPE_SYSTEM : 0) | (dialog.isBoxChecked(R.id.user_apps) ? APP_TYPE_USER : 0); type = (dialog.isBoxChecked(R.id.activities) ? PackageManager.GET_ACTIVITIES : 0) | (dialog.isBoxChecked(R.id.receivers) ? PackageManager.GET_RECEIVERS : 0) | (dialog.isBoxChecked(R.id.services) ? PackageManager.GET_SERVICES : 0) | (dialog.isBoxChecked(R.id.content_providers) ? PackageManager.GET_PROVIDERS : 0); protection = (dialog.isBoxChecked(R.id.permission_filter_world_accessible) ? PROTECTION_WORLD_ACCESSIBLE : 0) | (dialog.isBoxChecked(R.id.permission_filter_normal) ? PROTECTION_NORMAL : 0) | (dialog.isBoxChecked(R.id.permission_filter_dangerous) ? PROTECTION_DANGEROUS : 0) | (dialog.isBoxChecked(R.id.permission_filter_signature) ? PROTECTION_SIGNATURE : 0) | (dialog.isBoxChecked(R.id.permission_filter_system) ? PROTECTION_SYSTEM : 0) | (dialog.isBoxChecked(R.id.permission_filter_development) ? PROTECTION_DEVELOPMENT : 0) | (dialog.isBoxChecked(R.id.permission_filter_unexported) ? PROTECTION_UNEXPORTED : 0) | (dialog.isBoxChecked(R.id.permission_filter_unknown) ? PROTECTION_UNKNOWN : 0); includeOnlyProvidersAllowingPermissionGranting = dialog .isBoxChecked(R.id.only_providers_with_grant_uri_permission); boolean requireMetaData = dialog.isBoxChecked(R.id.metadata); requireMetaDataSubstring = requireMetaData ? dialog.getTextFromField(R.id.metadata_substring) : null; testWritePermissionForProviders = dialog.isBoxChecked(R.id.write_permission); } // Verification @Override boolean isExcludingEverything() { return appType == 0 || type == 0 || protection == 0; } // // Parcelable // @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt((testWritePermissionForProviders ? 2 : 0)); dest.writeInt(type); dest.writeInt(appType); dest.writeInt(protection); dest.writeString(requireMetaDataSubstring); } public static final Creator<ComponentFetcher> CREATOR = new Creator<ComponentFetcher>() { @Override public ComponentFetcher createFromParcel(Parcel source) { int flags = source.readInt(); ComponentFetcher fetcher = new ComponentFetcher(); fetcher.type = source.readInt(); fetcher.appType = source.readInt(); fetcher.protection = source.readInt(); fetcher.requireMetaDataSubstring = source.readString(); fetcher.testWritePermissionForProviders = (flags & 2) != 0; return fetcher; } @Override public ComponentFetcher[] newArray(int size) { return new ComponentFetcher[size]; } }; // Options menu @Override void onPrepareOptionsMenu(Menu menu) { if (appType == APP_TYPE_USER) { menu.findItem(R.id.system_apps).setVisible(true); } else if (appType == APP_TYPE_SYSTEM) { menu.findItem(R.id.user_apps).setVisible(true); } if (type == PackageManager.GET_ACTIVITIES) { menu.findItem(R.id.activities).setChecked(true); } else if (type == PackageManager.GET_RECEIVERS) { menu.findItem(R.id.broadcasts).setChecked(true); } else if (type == PackageManager.GET_SERVICES) { menu.findItem(R.id.services).setChecked(true); } else if (type == PackageManager.GET_PROVIDERS) { menu.findItem(R.id.content_providers).setChecked(true); } menu.findItem(R.id.simple_filter_permission).setVisible(true); for (int i = 0; i < PROTECTION_PRESETS_MENU_IDS.length; i++) { if (protection == PROTECTION_PRESETS[i]) { menu.findItem(PROTECTION_PRESETS_MENU_IDS[i]).setChecked(true); } } } @Override boolean onOptionsItemSelected(int id) { switch (id) { case R.id.system_apps: appType = APP_TYPE_SYSTEM; return true; case R.id.user_apps: appType = APP_TYPE_USER; return true; case R.id.activities: type = PackageManager.GET_ACTIVITIES; return true; case R.id.broadcasts: type = PackageManager.GET_RECEIVERS; return true; case R.id.services: type = PackageManager.GET_SERVICES; return true; case R.id.content_providers: type = PackageManager.GET_PROVIDERS; return true; case R.id.permission_filter_all: protection = PROTECTION_ANY; return true; case R.id.permission_filter_exported: protection = PROTECTION_ANY_EXPORTED; return true; case R.id.permission_filter_obtainable: protection = PROTECTION_ANY_OBTAINABLE; return true; case R.id.permission_filter_world_accessible: protection = PROTECTION_WORLD_ACCESSIBLE; return true; } return false; } // JSON serialization & name static final Descriptor DESCRIPTOR = new Descriptor(ComponentFetcher.class, "components", R.string.components) { @Override Fetcher unserializeFromJSON(JSONObject jsonObject) throws JSONException { ComponentFetcher fetcher = new ComponentFetcher(); fetcher.type = jsonObject.getInt("componentType"); fetcher.appType = jsonObject.getInt("appType"); fetcher.protection = jsonObject.getInt("protectionFilter"); if ((fetcher.type & PackageManager.GET_PROVIDERS) != 0) { fetcher.testWritePermissionForProviders = jsonObject.getBoolean("testProviderWrite"); } fetcher.requireMetaDataSubstring = jsonObject.getString("metadataSubstring"); return fetcher; } }; @Override JSONObject serializeToJSON() throws JSONException { JSONObject jsonObject = new JSONObject(); jsonObject.put("componentType", type); jsonObject.put("appType", appType); jsonObject.put("protectionFilter", protection); if ((type & PackageManager.GET_PROVIDERS) != 0) { jsonObject.put("testProviderWrite", testWritePermissionForProviders); } jsonObject.put("metadataSubstring", requireMetaDataSubstring); return jsonObject; } }