I am implementing custom turn-by-turn navigation in my android application. To achieve this, I have started the activity from my MainActivity using an intent which uses Intent.ACTION_VIEW as action and "google.navigation:q" as uri string.The google maps navigation page is successfully loaded in my app.
But, I don't know how to gracefully exit from this page. If I use back button press, it takes 4 back button clicks to display my main activity screen. Is there any possibility to place "exit" button in this page.
I have tried "onActivityForResult" and "onBackPressed" for destroying the google maps screens. None of this works. Please provide some suggestions to go further.
I know I am pretty late to answer this but maybe it can help someone.
You cannot come back from google map to your activity/app on single back press for this you need to create a floating view/widget like ola/uber which will do this for you after proper implementation. Here is my implementation.
First the user will go to map app from YourActivity. In this activity we will ask the permission for SYSTEM_ALERT_WINDOW (DRAW OVER, for SDK > MarshMallow) on click of some view. Then we will launch google map as well as a Service created by us to create a floating icon.
class YourActivity extends AppCompatActivity{
private GetFloatingIconClick mGetServiceClick;
public static boolean isFloatingIconServiceAlive = false;
onCreate(){
mGetServiceClick = new GetFloatingIconClick();
somebtn.onclick(){
askDrawOverPermission();
}
}
private class GetFloatingIconClick extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent selfIntent = new Intent(YourActivity.this, YourActivity.class);
selfIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(selfIntent);
}
}
private void askDrawOverPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// if OS is pre-marshmallow then create the floating icon, no permission is needed
createFloatingBackButton();
} else {
if (!Settings.canDrawOverlays(this)) {
// asking for DRAW_OVER permission in settings
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getApplicationContext().getPackageName()));
startActivityForResult(intent, REQ_CODE_DRAW_OVER);
} else {
createFloatingBackButton();
}
}
}
// starting service for creating a floating icon over map
private void createFloatingBackButton() {
Intent iconServiceIntent = new Intent(YourActivity.this, FloatingOverMapIconService.class);
iconServiceIntent.putExtra("RIDE_ID", str_rideId);
Intent navigation = new Intent(Intent.ACTION_VIEW, Uri
.parse("google.navigation:q=" + lat_DEST + "," + lng_DEST + "&mode=d"));
navigation.setPackage("com.google.android.apps.maps");
startActivityForResult(navigation, 1234);
startService(iconServiceIntent);
}
#TargetApi(Build.VERSION_CODES.M)
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQ_CODE_DRAW_OVER) {
// as permissions from Settings don't provide any callbacks, hence checking again for the permission
// so that we can draw our floating without asking user to click on the previously clicked view
// again
if (Settings.canDrawOverlays(this)) {
createFloatingBackButton();
} else {
//permission is not provided by user, do your task
//GlobalVariables.alert(mContext, "This permission is necessary for this application's functioning");
}
} else if (requestCode == 1234) {
// no result is returned by google map, as google don't provide any apis or documentation
// for it.
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}
Service Class:-
public class FloatingOverMapIconService extends Service {
private WindowManager windowManager;
private FrameLayout frameLayout;
private String str_ride_id;
public static final String BROADCAST_ACTION = "com.yourpackage.YourActivity";
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
createFloatingBackButton();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// to receive any data from activity
str_ride_id = intent.getStringExtra("RIDE_ID");
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
windowManager.removeView(frameLayout);
}
private void createFloatingBackButton() {
CurrentJobDetail.isFloatingIconServiceAlive = true;
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
frameLayout = new FrameLayout(this);
LayoutInflater layoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
// Here is the place where you can inject whatever layout you want in the frame layout
layoutInflater.inflate(R.layout.custom_start_ride_back_button_over_map, frameLayout);
ImageView backOnMap = (ImageView) frameLayout.findViewById(R.id.custom_drawover_back_button);
backOnMap.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(BROADCAST_ACTION);
intent.putExtra("RIDE_ID", str_ride_id);
sendBroadcast(intent);
//stopping the service
FloatingOverMapIconService.this.stopSelf();
CurrentJobDetail.isFloatingIconServiceAlive = false;
}
});
windowManager.addView(frameLayout, params);
}
}
Floating Icon Xml:-
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="#+id/custom_drawover_back_button"
android:layout_width="70dp"
android:layout_height="100dp"
android:src="#drawable/common_full_open_on_phone"
android:scaleType="center"
android:background="#color/colorAccent"/>
</LinearLayout>
Manifest file :-
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<activity
android:name=".Activities.YourActivity"
android:launchMode="singleTop" />
<service
android:name=".Utils.FloatingOverMapIconService"
android:exported="false" />
Related
I am working on an application where i need to navigate to specific activity based on some actions. Here is a image
Here my first activity is the ReadingActivity. Based on some actions user will be taken to the NewProjectReadingActivity. There the user will have Two options.
Option One
Option Two
Based on the options chosen by the user he/she will be taken to the ReadingInputActivity. After taking the input from the ReadingInputActivity the user will be forwarded to the ReadingConfirmationActivity. There will be three options
Option One
Option Two
Option Three
If the user selects the Option One then he/she will be taken to the ReadingActivity which is very easy. I will clear the stack and start the ReadingActivity again and there if he/she presses the back button the app will be minimized which is totally fine. But if the user selects Option Two then he/she will be taken to the NewProjectReadingActivity.If i clear the activity stack and start NewProjectReadingActivity again then it will start the NewProjectReadingActivity but the problem is that if the user presses the back button it will minimize my app as it is the only Activity present in the activity stack.
What i want is that if the user selects Option Two on the ReadingConfirmationActivity then the user will be taken to the NewReadingActivity that means i want to remove ReadingConfirmationActivity (which is easy just call the finish()) and the activity started before that activity i,e ReadingInputActivity.
Every Activity mentioned above is hosting a fragment. I am providing the activity code below.
ReadingActivity
public class ReadingActivity extends BaseAppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if( savedInstanceState == null ) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, ReadingFragment.getInstance(), ReadingFragment.TAG).commit();
}
setDisplayHomeAsUpEnabled(true);
setActionBarText(getString(R.string.new_project_reading));
}
#Override
protected void setupContentView() {
setContentView(R.layout.activity_reading);
}
public static Intent newIntent(Context context) {
Intent intent = new Intent(context, ReadingActivity.class);
return intent;
}
}
NewProjectReadingActivity
public class NewProjectReadingActivity extends BaseAppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if( savedInstanceState == null ) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, NewProjectReadingFragment.getInstance(), NewProjectReadingFragment.TAG).commit();
}
setDisplayHomeAsUpEnabled(true);
setActionBarText(getString(R.string.new_project_reading));
}
#Override
protected void setupContentView() {
setContentView(R.layout.activity_new_project_reading);
}
public static Intent newIntent(Context context) {
Intent intent = new Intent(context, NewProjectReadingActivity.class);
return intent;
}
}
ReadingInputActivity
public class ReadingInputActivity extends BaseAppCompatActivity {
private static final String EXTRA_VALUE_TYPE = "value_type";
private int valueType = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
valueType = getIntent().getIntExtra(EXTRA_VALUE_TYPE, 0);
if( savedInstanceState == null ) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, ReadingInputFragment.getInstance(valueType), ReadingInputFragment.TAG).commit();
}
setDisplayHomeAsUpEnabled(true);
setActionBarText(getString(R.string.reading_input));
}
#Override
protected void setupContentView() {
setContentView(R.layout.activity_reading_input);
}
public static Intent newIntent(Context context, int valueType) {
Intent intent = new Intent(context, ReadingInputActivity.class);
intent.putExtra(EXTRA_VALUE_TYPE, valueType);
return intent;
}
}
ReadingConfirmationActivity
public class ReadingConfirmationActivity extends BaseAppCompatActivity {
private static final String EXTRA_VALUE_TYPE = "value_type";
private static final String EXTRA_READING_VALUE = "reading_value";
private int valueType = 0;
private double readingValue = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
valueType = getIntent().getIntExtra(EXTRA_VALUE_TYPE, 0);
readingValue = getIntent().getDoubleExtra(EXTRA_READING_VALUE, 0);
if( savedInstanceState == null ) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, ReadingConfirmationFragment.getInstance(valueType, readingValue), ReadingConfirmationFragment.TAG).commit();
}
setDisplayHomeAsUpEnabled(true);
setActionBarText(getString(R.string.reading_input));
}
#Override
protected void setupContentView() {
setContentView(R.layout.activity_reading_confirmation);
}
public static Intent newIntent(Context context, int valueType, double readingValue) {
Intent intent = new Intent(context, ReadingConfirmationActivity.class);
intent.putExtra(EXTRA_VALUE_TYPE, valueType);
intent.putExtra(EXTRA_READING_VALUE, readingValue);
return intent;
}
}
You wrote:
What i want is that if the user selects Option Two on the
ReadingConfirmationActivity then the user will be taken to the
NewReadingActivity that means i want to remove
ReadingConfirmationActivity (which is easy just call the finish()) and
the activity started before that activity i,e ReadingInputActivity.
To do this you will do the following when the user selects Option Two in ReadingConfirmationActivity:
Intent intent = new Intent(this, NewReadingActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
This will cause all of the activities on top (and including) NewReadingActivity to be finished. A new instance of NewReadingActivity will be created and shown to the user. If you wish to return to the existing instance of NewReadingActivity, you can also do that by also adding Intent.FLAG_ACTIVITY_SINGLE_TOP to the Intent.
I have simple activity with one button. When button is clicked, I'm firing intent to pick image from gallery. Something strange is going on when I fire intent and then rotate the screen. Here are the steps:
Click button. "Open with" system dialog appears.
Rotate screen. Activity gets recreated, dialog is still shown. Note - I don't call startActivityForResult(Intent, int) again on my activity recreate.
Tap back button. "Open with" dialog disappear, but there is another one beneath it.
So it seems even though I don't call startActivityForResult(Intent, int), new instance of dialog gets created every orientation change, and old instance don't getting destroyed.
Does anyone facing this issue? How to get rid of these duplicate dialogs?
Update 1:
So, here is some sample code:
public class MainActivity extends AppCompatActivity
{
private boolean mIsStarted = false;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null)
{
mIsStarted = savedInstanceState.getBoolean("key");
}
if (!mIsStarted)
{
mIsStarted = true;
Intent intent = new Intent(Intent.ACTION_PICK).setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MimeType.IMAGE);
startActivityForResult(intent, 1);
}
}
#Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putBoolean("key", mIsStarted);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
mIsStarted = false;
}
}
I also tried to set android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc" and still every time I rotate the screen, new copy of dialog (actually this is not a dialog, this is ResolverActivity) being showed on top of previous one. Is this some Android bug or it's just me doing something wrong?
Update 2: so I tried another approach - call finishActivity(int) inside my Activity.onStop(). Result is pretty strange - now I've got only 2 copies of ChooserActivity. After second copy is created, it starts rotating fine.
Here is the code:
public class MainActivity extends AppCompatActivity
{
private static final String LOG_TAG = MainActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState)
{
Log.d(LOG_TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.make_photo).setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
Intent intent = new Intent(Intent.ACTION_PICK).setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MimeType.IMAGE);
startActivityForResult(intent, 1);
}
});
}
#Override
protected void onStop()
{
Log.d(LOG_TAG, "onStop");
super.onStop();
finishActivity(1);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
Log.d(LOG_TAG, String.format("onActivityResult[requestCode:%d, resultCode:%d, data:%s]",
requestCode, resultCode, data != null ? data.toString() : "NULL"));
}
}
Still wonder why second copy is being created.
Add this to the activity declatation in your androidmanifest.xml
android:configChanges="orientation|screenSize"
This will prevent the activity from getting recreated and resolve the issue of duplicate dialog.
Find out this was silly mistake in my base Activity class. I end up with deriving all my Activities which works with intents from this base IntentProcessingActivity class.
/**
* Base activity for all activities which process intents. This activity saves processing state
* during recreation, so derived activities can get rid of this. This is useful for not showing
* "Open with" dialogs multiple times.
* <p />
* Derived activities can check if some intent is currently processing with {#link
* #isProcessingIntent()} function.
* <p />
* Created by Maksimov Stanislav (s.maks04#gmail.com) on 25.01.16
*/
public class IntentProcessingActivity extends AppCompatActivity
{
private static final String KEY_IS_PROCESSING_INTENT = "IsProcessingIntent";
private boolean mIsProcessingIntent;
#CallSuper
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
mIsProcessingIntent = savedInstanceState.getBoolean(KEY_IS_PROCESSING_INTENT, false);
}
}
#CallSuper
#Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_IS_PROCESSING_INTENT, mIsProcessingIntent);
}
#CallSuper
#Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options)
{
mIsProcessingIntent = true;
super.startActivityForResult(intent, requestCode, options);
}
#CallSuper
#Override
public void startActivityForResult(Intent intent, int requestCode)
{
mIsProcessingIntent = true;
super.startActivityForResult(intent, requestCode);
}
#CallSuper
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
mIsProcessingIntent = false;
super.onActivityResult(requestCode, resultCode, data);
}
protected final boolean isProcessingIntent()
{
return mIsProcessingIntent;
}
}
On child Activities I just check
if (!isProcessingIntent())
{
startActivityForResult(...);
}
Code:
Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.addCategory(Intent.CATEGORY_HOME);
Intent chooser = Intent.createChooser(launchIntent, "Complete Action using..");
activity.startActivity(chooser);
I don't see any way to tell which Intent (HOME category launcher) was selected. There is no Intent.addOnActionSetListener, and no chooser.addOnIntentChosenListener etc.
So how can I tell which was selected? Do I have to write my own chooser for this?
On Android 5.1+, you can use the three-parameter edition of the createChooser() method, where the last parameter is an IntentSender that you can use to find out what was chosen.
Prior to Android 5.1, there is nothing in Android to let you know what the user chose.
The answer provided by BinHe works but the problem is that a big number of apps is shown. In this solution I use the Intent.ACTION_PICK_ACTIVITY but only the apps compatible with Intent.ACTION_SEND will be shown, and you will know which option the user selected.
public void doSocialShare(String title, String text, String url){
// First search for compatible apps with sharing (Intent.ACTION_SEND)
List<Intent> targetedShareIntents = new ArrayList<Intent>();
Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
shareIntent.setType("text/plain");
// Set title and text to share when the user selects an option.
shareIntent.putExtra(Intent.EXTRA_TITLE, title);
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
shareIntent.putExtra(Intent.EXTRA_TEXT, text);
List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(shareIntent, 0);
if (!resInfo.isEmpty()) {
for (ResolveInfo info : resInfo) {
Intent targetedShare = new Intent(android.content.Intent.ACTION_SEND);
targetedShare.setType("text/plain"); // put here your mime type
targetedShare.setPackage(info.activityInfo.packageName.toLowerCase());
targetedShareIntents.add(targetedShare);
}
// Then show the ACTION_PICK_ACTIVITY to let the user select it
Intent intentPick = new Intent();
intentPick.setAction(Intent.ACTION_PICK_ACTIVITY);
// Set the title of the dialog
intentPick.putExtra(Intent.EXTRA_TITLE, title);
intentPick.putExtra(Intent.EXTRA_INTENT, shareIntent);
intentPick.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray());
// Call StartActivityForResult so we can get the app name selected by the user
this.startActivityForResult(intentPick, REQUEST_CODE_MY_PICK);
}
}
Finally, to be able to get the app selected by the user you must override the onActivityResult on your activity:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_CODE_MY_PICK) {
if(data != null && data.getComponent() != null && !TextUtils.isEmpty(data.getComponent().flattenToShortString()) ) {
String appName = data.getComponent().flattenToShortString();
// Now you know the app being picked.
// data is a copy of your launchIntent with this important extra info added.
// Start the selected activity
startActivity(data);
}
}
}
This should work for early versions of Android.
Use intent PICKER instead of CHOOSER. The difference is that picker won't start the target intent automatically, but rather, it returns to onActivityResult() the target intent with the selected app's component name attached. Then you start the target intent in the callback as a 2nd step.
A little bit of code should explain,
// In MyActivity class
static final int REQUEST_CODE_MY_PICK = 1;
// Getting ready to start intent. Note: call startActivityForResult()
... launchIntent = the target intent you want to start;
Intent intentPick = new Intent();
intentPick.setAction(Intent.ACTION_PICK_ACTIVITY);
intentPick.putExtra(Intent.EXTRA_TITLE, "Launch using");
intentPick.putExtra(Intent.EXTRA_INTENT, launchIntent);
this.startActivityForResult(intentPick, REQUEST_CODE_MY_PICK);
// You have just started a picker activity,
// let's see what user will pick in the following callback
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == REQUEST_CODE_MY_PICK) {
String appName = data.getComponent().flattenToShortString();
// Now you know the app being picked.
// data is a copy of your launchIntent with this important extra info added.
// Don't forget to start it!
startActivity(data);
}
}
I did in different way, no need to implement custom component:
Send Intent:
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "My feature text");
sendIntent.setType("text/plain");
Intent receiver = new Intent(this, ApplicationSelectorReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, receiver, PendingIntent.FLAG_UPDATE_CURRENT);
Intent chooser = Intent.createChooser(sendIntent, null, pendingIntent.getIntentSender());
startActivity(chooser);
Add BroadcastReceiver ApplicationSelectorReceiver.class in manifest.
<receiver android:name=".ApplicationSelectorReceiver"></receiver>
ApplicationSelectorReceiver.java
public class ApplicationSelectorReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
for (String key : Objects.requireNonNull(intent.getExtras()).keySet()) {
try {
ComponentName componentInfo = (ComponentName) intent.getExtras().get(key);
PackageManager packageManager = context.getPackageManager();
assert componentInfo != null;
String appName = (String) packageManager.getApplicationLabel(packageManager.getApplicationInfo(componentInfo.getPackageName(), PackageManager.GET_META_DATA));
Log.i("Selected Application Name", appName);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Result:
Gmail
Facebook
Hangouts
Instagram
Drive
Hope this would help others.
The CommonsWare's solution only works from Android 5.1 (API level 22). Here is my solution to work with all Android versions, by creating our own app chooser dialog.
Step 1: Create a custom layout for the app chooser dialog.
dialog_app_chooser.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/text_view_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="20dp"
android:paddingTop="14dp"
android:paddingEnd="0dp"
android:paddingBottom="14dp"
android:text="Select an action"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view_apps"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="16dp"
android:paddingEnd="16dp" />
</LinearLayout>
Step 2: Create a layout for an item in app chooser dialog.
item_app.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/layout_app"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="#+id/image_view_app_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" />
<TextView
android:id="#+id/text_view_app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="20dp"
android:textColor="#000"
android:textSize="12sp" />
</LinearLayout>
Step 3: Create a model class which indicates an app in the app chooser dialog.
App.java
public class App implements Parcelable {
public Intent intent;
public ResolveInfo resolveInfo;
public App(Intent intent, ResolveInfo resolveInfo) {
this.intent = intent;
this.resolveInfo = resolveInfo;
}
protected App(Parcel in) {
intent = in.readParcelable(Intent.class.getClassLoader());
resolveInfo = in.readParcelable(ResolveInfo.class.getClassLoader());
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(intent, flags);
dest.writeParcelable(resolveInfo, flags);
}
public static final Creator<App> CREATOR = new Creator<App>() {
#Override
public App createFromParcel(Parcel in) {
return new App(in);
}
#Override
public App[] newArray(int size) {
return new App[size];
}
};
}
Step 4: Create a custom adapter which display all apps in the app chooser dialog.
AppAdapter.java
public class AppAdapter extends RecyclerView.Adapter<AppAdapter.ViewHolder> {
private List<App> apps;
private OnItemClickListener listener;
public AppAdapter(List<App> apps, OnItemClickListener listener) {
this.apps = apps;
this.listener = listener;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int position) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_app, parent, false);
return new ViewHolder(view, listener);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, int position) {
App app = apps.get(viewHolder.getAdapterPosition());
viewHolder.bind(app);
}
#Override
public int getItemCount() {
return apps.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private ImageView appIcon;
private TextView appName;
private App app;
ViewHolder(View itemView, final OnItemClickListener listener) {
super(itemView);
appIcon = itemView.findViewById(R.id.image_view_app_icon);
appName = itemView.findViewById(R.id.text_view_app_name);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
listener.onItemClick(app);
}
});
}
void bind(App app) {
this.app = app;
PackageManager packageManager = appName.getContext().getPackageManager();
appIcon.setImageDrawable(app.resolveInfo.loadIcon(packageManager));
appName.setText(app.resolveInfo.loadLabel(packageManager));
}
}
interface OnItemClickListener {
void onItemClick(App app);
}
}
Step 5: Create the app chooser dialog.
AppChooserDialog.java
public class AppChooserDialog extends BottomSheetDialogFragment implements AppAdapter.OnItemClickListener {
private static final String KEY_APPS = "KEY_APPS";
private static final String KEY_TITLE = "KEY_TITLE";
private static final String KEY_REQUEST_CODE = "KEY_REQUEST_CODE";
public static void show(AppCompatActivity activity, ArrayList<Intent> targets, String title, int requestCode) {
PackageManager packageManager = activity.getPackageManager();
ArrayList<App> apps = new ArrayList<>();
for (Intent intent : targets) {
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
for (ResolveInfo resolveInfo : activities) {
Intent targetIntent = new Intent(intent);
apps.add(new App(targetIntent, resolveInfo));
}
}
if (apps.size() > 0) {
if (apps.size() == 1) {
activity.startActivityForResult(apps.get(0).intent, requestCode);
} else {
DialogFragment appChooserDialog = new AppChooserDialog();
Bundle data = new Bundle();
data.putParcelableArrayList(KEY_APPS, apps);
data.putString(KEY_TITLE, title);
data.putInt(KEY_REQUEST_CODE, requestCode);
appChooserDialog.setArguments(data);
appChooserDialog.show(activity.getSupportFragmentManager(), "AppChooserDialog");
}
}
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.dialog_app_chooser, container, false);
TextView titleTextView = rootView.findViewById(R.id.text_view_title);
RecyclerView appsRecyclerView = rootView.findViewById(R.id.recycler_view_apps);
String title = getArguments().getString(KEY_TITLE);
if (!TextUtils.isEmpty(title)) {
titleTextView.setText(title);
}
List<App> apps = getArguments().getParcelableArrayList(KEY_APPS);
appsRecyclerView.setAdapter(new AppAdapter(apps, this));
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
float screenWidthInDp = displayMetrics.widthPixels / displayMetrics.density;
int recyclerViewItemWidthInDp = 72;
int recyclerViewStartEndPadding = 32;
int numberOfColumns = (int) ((screenWidthInDp - recyclerViewStartEndPadding) / recyclerViewItemWidthInDp);
int spanCount = (apps.size() < numberOfColumns) ? apps.size() : numberOfColumns;
appsRecyclerView.setLayoutManager(new GridLayoutManager(requireActivity(), spanCount));
return rootView;
}
#Override
public void onItemClick(App app) {
ActivityInfo activity = app.resolveInfo.activityInfo;
String packageName = activity.applicationInfo.packageName;
ComponentName component = new ComponentName(packageName, activity.name);
Intent intent = new Intent(app.intent);
intent.setComponent(component);
Uri uri = app.intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
if (uri != null) {
requireActivity().grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
OnAppSelectedListener listener = null;
try {
listener = (OnAppSelectedListener) requireActivity();
} catch (Exception e) {
// Ignore exception
}
if (listener != null) {
listener.onAppSelected(intent);
}
requireActivity().startActivityForResult(intent, getArguments().getInt(KEY_REQUEST_CODE));
dismiss();
}
public interface OnAppSelectedListener {
void onAppSelected(Intent intent);
}
}
Step 6: Using the app chooser dialog from an activity.
public class MainActivity extends AppCompatActivity implements AppChooserDialog.OnAppSelectedListener {
private static final int REQUEST_CODE_PICK_IMAGE = 100;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayList<Intent> intents = new ArrayList<>();
Intent pickImageIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intents.add(pickImageIntent);
AppChooserDialog.show(this, intents, "Pick image from", REQUEST_CODE_PICK_IMAGE);
}
#Override
public void onAppSelected(Intent intent) {
ComponentName componentName = intent.getComponent();
String packageName = componentName.getPackageName();
String activityName = componentName.getClassName();
Log.i("TAG", "packageName = " + packageName + ", activityName = " + activityName);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_PICK_IMAGE) {
Log.i("TAG", "onActivityResult");
// TODO: Put your logic here.
}
}
}
The result:
I'm working from Unity and want to get the result of a file picking activity. Since I can't start activities from Unity itself, I created an "intermediant" static class called EclipseIntegration:
package com.Unity3D.EclipseIntegration;
public class EclipseIntegration {
public static String selectedPath = "really ";
public static void Launch(Activity activity) {
Intent target = new Intent(activity, MyActivity.class);
activity.startActivity(target);
}
}
activity is the Activity Unity is currently running in.
MyActivity is meant to start the real activity, because I need something that inherits from Activity to get the onActivityResult event:
package com.Unity3D.EclipseIntegration;
public class MyActivity extends Activity{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Launch();
}
public void Launch() {
Intent target = FileUtils.createGetContentIntent();
Intent intent = Intent.createChooser(target, "Select a movie");
startActivityForResult(intent);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 128:
if (resultCode == RESULT_OK) {
final Uri uri = data.getData();
File file = FileUtils.getFile(uri);
EclipseIntegration.selectedPath = file.getAbsolutePath();
}
else EclipseIntegration.selectedPath = "";
finish();
}
}
}
but nothing happens. If I start the final activity directly in the static class like this :
activity.startActivity(intent); it works though, so I suppose I made some error while setting up the MyActivity? Despite trying this for 2 days now I couldn't find it though, so any nudge in the right direction would be very appreciated :)
Thanks,
kiriri
Try this way
public void Launch() {
Intent target = FileUtils.createGetContentIntent();
Intent intent = Intent.createChooser(target, "Select a movie");
startActivityForResult(intent,128);
}
I am trying to get the response from activate device admin by user.
if the user press activate then do somethings or if the user press cancel a dialog will pop to inform user should activate.
i try to use
In Class DeviceAdmin
public class DeviceAdmin {
public static final int DEVICE_ADMIN_REQUEST = 9;
public static final int START_SERVICE = 8;
private static DevicePolicyManager mDevicePolicyManager;
private static ComponentName mComponentName;
public static DevicePolicyManager getDevicePolicyManager() {
return mDevicePolicyManager;
}
public static void setDevicePolicyManager(
final DevicePolicyManager devicePolicyManager) {
mDevicePolicyManager = devicePolicyManager;
}
public static ComponentName getComponentName() {
return mComponentName;
}
public static void setComponentName(final ComponentName componentName) {
mComponentName = componentName;
}
public static void initDPM(final Activity activity) {
if (mDevicePolicyManager == null) {
setDevicePolicyManager((DevicePolicyManager) activity
.getSystemService(Context.DEVICE_POLICY_SERVICE));
}
}
public static <T> void initComponent(final Activity activity,
final Class<T> reciever) {
if (mComponentName == null) {
setComponentName(new ComponentName(activity, reciever));
}
}
public static boolean isDeviceAdmin() {
return mDevicePolicyManager.isAdminActive(mComponentName);
}
public static void registerDeviceAdmin(final Activity activity,
final int requestCode) {
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
getComponentName());
activity.startActivityForResult(intent, requestCode);
}
public static void unregisterDeviceAdmin() {
if (mDevicePolicyManager.isAdminActive(mComponentName))
mDevicePolicyManager.removeActiveAdmin(mComponentName);
}
}
In MainActivity
public class LockIt extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
if (!DeviceAdmin.isDeviceAdmin())
DeviceAdmin.registerDeviceAdmin(this,
DeviceAdmin.DEVICE_ADMIN_REQUEST);
else
// Do something;
}
private void init() {
DeviceAdmin.initDPM(this);
DeviceAdmin.initComponent(this, LockItNowAdminReceiver.class);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == DeviceAdmin.DEVICE_ADMIN_REQUEST) {
if (resultCode == RESULT_OK) {
// do something;
} else if (resultCode == RESULT_CANCELED) {
// showAdminDialog;
}
}
}
}
RESULT_OK is -1, RESULT_CANCELED is 0
But when I ran in debug mode, I found that no matter user press Cancel or Activate,
resultCode is always 0.
UPDATE
OKAY, I think I have found the reason. I used android:launchMode="singleInstance" in Manifest of MainActivity
Do this is a proper way to get the response of device admin activation?
Or I have do something wrong?
Yes, that's true, but you should put an instance of a class extending DeviceAdminReceiver:
ComponentName mDeviceAdminSample = new ComponentName(v.getContext(), DeviceAdmin.class);
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample);
activity.startActivityForResult(intent, DEVICE_ADMIN_REQUEST);
And in onAcitivityResult:
if (requestCode == DEVICE_ADMIN_REQUEST}
if(resultCode == RESULT_OK) {
// admin
} else {
// not admin
}
}
A complete sample can be found here
Just get sure of declaring your receiver in the AndroidManifest.XML.
Finally
I think I have found the reason.
I used android:launchMode="singleInstance" in Manifest of MainActivity
Now I use android:taskAffinity.
It seems everything okay now.