blob: 662f24895d65156b4c0cdf78b3e555522a86c565 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* 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.example.android.intentplayground;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Static utility functions to query intent and activity manifest flags.
*/
class FlagUtils {
private static Class<Intent> sIntentClass = Intent.class;
private static List<ActivityInfo> sActivityInfos = null;
private static Intent sIntent = new Intent();
static final String INTENT_FLAG_PREFIX = "FLAG_ACTIVITY";
private static final String ACTIVITY_INFO_FLAG_PREFIX = "FLAG";
/**
* Returns a String list of flags active on this intent.
* @param intent The intent on which to query flags.
* @return A list of flags active on this intent.
*/
public static List<String> discoverFlags(Intent intent) {
int flags = intent.getFlags();
return Arrays.stream(intent.getClass().getDeclaredFields()) // iterate over Intent members
.filter(f -> f.getName().startsWith(INTENT_FLAG_PREFIX)) // filter FLAG_ fields
.filter(f -> {
try {
return (flags & f.getInt(intent)) > 0;
} catch (IllegalAccessException e) {
// Should never happen, the fields we are reading are public
throw new RuntimeException(e);
}
}) // filter fields that are present in intent
.map(Field::getName) // map present Fields to their string names
.collect(Collectors.toList());
}
/**
* Returns a full list of flags available to be set on an intent.
* @return A string list of all intent flags.
*/
public static List<String> getIntentFlagsAsString() {
return Arrays.stream(sIntentClass.getDeclaredFields())
.filter(f -> f.getName().startsWith(INTENT_FLAG_PREFIX))
.map(Field::getName)
.collect(Collectors.toList());
}
/**
* Get all defined {@link IntentFlag}s.
* @return All defined IntentFlags.
*/
public static List<IntentFlag> getAllIntentFlags() {
return Arrays.asList(IntentFlag.values());
}
/**
* Get intent flags by category/
* @return List of string flags (value) organized by category/function (key).
*/
public static Map<String, List<String>> intentFlagsByCategory() {
Map<String, List<String>> categories = new HashMap<>();
List<String> allFlags = getIntentFlagsAsString();
List<String> nonUser = new LinkedList<>();
List<String> recentsAndUi = new LinkedList<>();
List<String> newTask = new LinkedList<>();
List<String> clearTask = new LinkedList<>();
List<String> rearrangeTask = new LinkedList<>();
List<String> other = new LinkedList<>();
allFlags.forEach(flag -> {
if (hasSuffix(flag, "BROUGHT_TO_FRONT", "LAUNCHED_FROM_HISTORY")) {
nonUser.add(flag);
} else if (hasSuffix(flag, "RECENTS", "LAUNCH_ADJACENT", "NO_ANIMATION", "NO_HISTORY",
"RETAIN_IN_RECENTS")) {
recentsAndUi.add(flag);
} else if (hasSuffix(flag, "MULTIPLE_TASK", "NEW_TASK", "NEW_DOCUMENT",
"RESET_TASK_IF_NEEDED")) {
newTask.add(flag);
} else if (hasSuffix(flag, "CLEAR_TASK", "CLEAR_TOP", "CLEAR_WHEN_TASK_RESET")) {
clearTask.add(flag);
} else if (hasSuffix(flag, "REORDER_TO_FRONT", "SINGLE_TOP", "TASK_ON_HOME")) {
rearrangeTask.add(flag);
} else {
other.add(flag);
}
});
categories.put("Non-user", nonUser);
categories.put("Recents and UI", recentsAndUi);
categories.put("New Task", newTask);
categories.put("Clear Task", clearTask);
categories.put("Rearrange Task", rearrangeTask);
categories.put("Other", other);
return categories;
}
/**
* Checks the target string for any of the listed suffixes.
* @param target The string to test for suffixes.
* @param suffixes The suffixes to test the string for.
* @return True if the target string has any of the suffixes, false if not.
*/
private static boolean hasSuffix(String target, String... suffixes) {
for (String suffix: suffixes) {
if (target.endsWith(suffix)) {
return true;
}
}
return false;
}
/**
* Gets the integer value of an intent flag.
* @param flagName The field name of the flag.
*/
public static int flagValue(String flagName) {
try {
return sIntentClass.getField(flagName).getInt(sIntent);
} catch (Exception e) {
return 0;
}
}
/**
* Checks if the passed intent has the specified flag.
* @param intent The intent of which to examine the flags.
* @param flagName The string name of the intent flag to check for.
* @return True if the flag is present, false if not.
*/
public static boolean hasIntentFlag(Intent intent, String flagName) {
return (intent.getFlags() & flagValue(flagName)) > 0;
}
/**
* Checks if the passed intent has the specified flag.
* @param intent The intent of which to examine the flags.
* @param flag The corresponding enum {@link IntentFlag} of the intent flag to check for.
* @return True if the flag is present, false if not.
*/
public static boolean hasIntentFlag(Intent intent, IntentFlag flag) {
return hasIntentFlag(intent, flag.getName());
}
/**
* Checks if the passed activity has the specified flag set in its manifest.
* @param context A context from this application (used to access {@link PackageManager}.
* @param activity The activity of which to examine the flags.
* @param flag The corresponding enum {@link ActivityFlag} of the activity flag to check for.
* @return True if the flag is present, false if not.
*/
public static boolean hasActivityFlag(Context context, ComponentName activity,
ActivityFlag flag) {
return getActivityFlags(context, activity).contains(flag);
}
/**
* Returns an {@link EnumSet} of {@link ActivityFlag} corresponding to activity manifest flags
* activity on the specified activity.
* @param context A context from this application (used to access {@link PackageManager}.
* @param activity The activity of which to examine the flags.
* @return A set of ActivityFlags corresponding to activity manifest flags.
*/
public static EnumSet<ActivityFlag> getActivityFlags(Context context, ComponentName activity) {
loadActivityInfo(context);
EnumSet<ActivityFlag> flags = EnumSet.noneOf(ActivityFlag.class);
Optional<ActivityInfo> infoOptional = sActivityInfos.stream()
.filter(i-> i.name.equals(activity.getClassName()))
.findFirst();
if (!infoOptional.isPresent()) {
return flags;
}
ActivityInfo info = infoOptional.get();
if ((info.flags & ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) > 0) {
flags.add(ActivityFlag.CLEAR_TASK_ON_LAUNCH);
}
if ((info.flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) > 0) {
flags.add(ActivityFlag.ALLOW_TASK_REPARENTING);
}
switch (info.launchMode) {
case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
flags.add(ActivityFlag.LAUNCH_MODE_SINGLE_INSTANCE);
break;
case ActivityInfo.LAUNCH_SINGLE_TASK:
flags.add(ActivityFlag.LAUNCH_MODE_SINGLE_TASK);
break;
case ActivityInfo.LAUNCH_SINGLE_TOP:
flags.add(ActivityFlag.LAUNCH_MODE_SINGLE_TOP);
break;
case ActivityInfo.LAUNCH_MULTIPLE:
default:
flags.add(ActivityFlag.LAUNCH_MODE_STANDARD);
break;
}
switch(info.documentLaunchMode) {
case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
flags.add(ActivityFlag.DOCUMENT_LAUNCH_MODE_INTO_EXISTING);
break;
case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
flags.add(ActivityFlag.DOCUMENT_LAUNCH_MODE_ALWAYS);
break;
case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
flags.add(ActivityFlag.DOCUMENT_LAUNCH_MODE_NEVER);
break;
case ActivityInfo.DOCUMENT_LAUNCH_NONE:
default:
flags.add(ActivityFlag.DOCUMENT_LAUNCH_MODE_NONE);
break;
}
return flags;
}
private static void loadActivityInfo(Context context) {
if (sActivityInfos == null) {
PackageInfo packInfo;
// Retrieve activities and their manifest flags
PackageManager pm = context.getPackageManager();
try {
packInfo = pm.getPackageInfo(context.getPackageName(),
PackageManager.GET_ACTIVITIES);
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
sActivityInfos = Arrays.asList(packInfo.activities);
}
}
/**
* Discover which flags on the specified {@link ActivityInfo} are enabled,
* and return them as a list of strings.
* @param activity The activity from which you want to find flags.
* @return A list of flags.
*/
public static List<String> getActivityFlags(ActivityInfo activity) {
int flags = activity.flags;
List<String> flagStrings = Arrays.stream(activity.getClass().getDeclaredFields())
.filter(f -> f.getName().startsWith(ACTIVITY_INFO_FLAG_PREFIX))
.filter(f -> {
try {
return (flags & f.getInt(activity)) > 0;
} catch (IllegalAccessException e) {
// Should never happen, the fields we are reading are public
throw new RuntimeException(e);
}
}) // filter fields that are present in intent
.map(Field::getName) // map present Fields to their string names
.map(name -> camelify(name.substring(ACTIVITY_INFO_FLAG_PREFIX.length())))
.map(s -> s.concat("=true"))
.collect(Collectors.toList());
// check for launchMode
if (activity.launchMode != 0) {
String lm = "launchMode=";
switch(activity.launchMode) {
case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
lm += "singleInstance";
break;
case ActivityInfo.LAUNCH_SINGLE_TASK:
lm += "singleTask";
break;
case ActivityInfo.LAUNCH_SINGLE_TOP:
lm += "singleTop";
break;
case ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK:
lm += "singleInstancePerTask";
break;
case ActivityInfo.LAUNCH_MULTIPLE:
default:
lm += "standard";
break;
}
flagStrings.add(lm);
}
// check for documentLaunchMode
if (activity.documentLaunchMode != 0) {
String dlm = "documentLaunchMode=";
switch(activity.documentLaunchMode) {
case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
dlm += "intoExisting";
break;
case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
dlm += "always";
break;
case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
dlm += "never";
break;
case ActivityInfo.DOCUMENT_LAUNCH_NONE:
default:
dlm += "none";
break;
}
flagStrings.add(dlm);
}
if (activity.taskAffinity != null) {
flagStrings.add("taskAffinity="+ activity.taskAffinity);
}
return flagStrings;
}
/**
* Takes a snake_case and converts to CamelCase.
* @param snake A snake_case string.
* @return A camelified string.
*/
public static String camelify(String snake) {
List<String> words = Arrays.asList(snake.split("_"));
StringBuilder output = new StringBuilder(words.get(0).toLowerCase());
words.subList(1,words.size()).forEach(s -> {
String first = s.substring(0,1).toUpperCase();
String rest = s.substring(1).toLowerCase();
output.append(first).append(rest);
});
return output.toString();
}
/**
* Retrieves the corresponding enum {@link IntentFlag} for the string flag.
* @param stringFlag the name of the intent flag.
* @return The corresponding IntentFlag.
*/
public static IntentFlag getFlagForString(String stringFlag) {
return getAllIntentFlags().stream().filter(flag -> flag.getName().equals(stringFlag)).findAny()
.orElse(null);
}
}