Move launch default activity prompt to the menu
Instead of asking at startup, the prompt now launches from a menu item. This also get's rid of the snackbar because it was causing bugs with rotation. Furthermore now the android.permission.SYSTEM_ALERT_WINDOW permission is no longer needed. Test: Manual Change-Id: I566e58d4844aecf127b5198c050ff115de7e4919
This commit is contained in:
@@ -18,8 +18,6 @@
|
|||||||
package="com.example.android.intentplayground">
|
package="com.example.android.intentplayground">
|
||||||
<!-- Used to reorder tasks using android.app.ActivityManager.moveTaskToFront() -->
|
<!-- Used to reorder tasks using android.app.ActivityManager.moveTaskToFront() -->
|
||||||
<uses-permission android:name="android.permission.REORDER_TASKS" />
|
<uses-permission android:name="android.permission.REORDER_TASKS" />
|
||||||
<!-- Used to display a persistent explanatory window while launching a series of activities -->
|
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
|
||||||
<application
|
<application
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
<menu
|
<menu
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/app_bar_launch"
|
||||||
|
android:title="@string/launch_default_activities"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/app_bar_test"
|
android:id="@+id/app_bar_test"
|
||||||
android:icon="@drawable/icon_tests"
|
android:icon="@drawable/icon_tests"
|
||||||
|
|||||||
@@ -88,4 +88,5 @@
|
|||||||
<string name="plural_activities_text">%d activities</string>
|
<string name="plural_activities_text">%d activities</string>
|
||||||
<string name="new_task">NEW</string>
|
<string name="new_task">NEW</string>
|
||||||
<string name="run_intent_tests">Run Intent tests</string>
|
<string name="run_intent_tests">Run Intent tests</string>
|
||||||
|
<string name="launch_default_activities">Launch Default Activities</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -16,20 +16,25 @@
|
|||||||
|
|
||||||
package com.example.android.intentplayground;
|
package com.example.android.intentplayground;
|
||||||
|
|
||||||
|
import static com.example.android.intentplayground.Node.newTaskNode;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ScrollView;
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,10 +44,12 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
|||||||
IntentBuilderView.OnLaunchCallback {
|
IntentBuilderView.OnLaunchCallback {
|
||||||
public final static String EXTRA_LAUNCH_FORWARD = "com.example.android.launchForward";
|
public final static String EXTRA_LAUNCH_FORWARD = "com.example.android.launchForward";
|
||||||
public final static String BUILDER_VIEW = "com.example.android.builderFragment";
|
public final static String BUILDER_VIEW = "com.example.android.builderFragment";
|
||||||
public static final String TREE_FRAGMENT = "com.example.android.treeFragment";
|
public static final String TREE_FRAGMENT = "com.example.android.treeFragment";
|
||||||
public static final String EXPECTED_TREE_FRAGMENT = "com.example.android.expectedTreeFragment";
|
public static final String EXPECTED_TREE_FRAGMENT = "com.example.android.expectedTreeFragment";
|
||||||
public static final int LAUNCH_REQUEST_CODE = 0xEF;
|
public static final int LAUNCH_REQUEST_CODE = 0xEF;
|
||||||
|
|
||||||
public enum Mode {LAUNCH, VERIFY, RESULT}
|
public enum Mode {LAUNCH, VERIFY, RESULT}
|
||||||
|
|
||||||
public boolean userLeaveHintWasCalled = false;
|
public boolean userLeaveHintWasCalled = false;
|
||||||
protected Mode mStatus = Mode.LAUNCH;
|
protected Mode mStatus = Mode.LAUNCH;
|
||||||
|
|
||||||
@@ -53,7 +60,9 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
|||||||
if (BuildConfig.DEBUG) Log.d(getLocalClassName(), "onCreate()");
|
if (BuildConfig.DEBUG) Log.d(getLocalClassName(), "onCreate()");
|
||||||
// Setup action bar
|
// Setup action bar
|
||||||
Toolbar appBar = findViewById(R.id.app_bar);
|
Toolbar appBar = findViewById(R.id.app_bar);
|
||||||
|
appBar.setTitle(this.getClass().getSimpleName());
|
||||||
setSupportActionBar(appBar);
|
setSupportActionBar(appBar);
|
||||||
|
|
||||||
loadMode(Mode.LAUNCH);
|
loadMode(Mode.LAUNCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +77,7 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the UI for the specified {@link Mode}.
|
* Initializes the UI for the specified {@link Mode}.
|
||||||
|
*
|
||||||
* @param mode The mode to display.
|
* @param mode The mode to display.
|
||||||
*/
|
*/
|
||||||
protected void loadMode(Mode mode) {
|
protected void loadMode(Mode mode) {
|
||||||
@@ -129,10 +139,57 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
|||||||
case R.id.app_bar_test:
|
case R.id.app_bar_test:
|
||||||
runIntentTests();
|
runIntentTests();
|
||||||
break;
|
break;
|
||||||
|
case R.id.app_bar_launch:
|
||||||
|
askToLaunchTasks();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void askToLaunchTasks() {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||||
|
.setMessage(R.string.launch_explanation)
|
||||||
|
.setTitle(R.string.ask_to_launch)
|
||||||
|
.setPositiveButton(R.string.ask_to_launch_affirm, (dialogInterface, i) -> {
|
||||||
|
setupTaskPreset().startActivities(TestBase.LaunchStyle.TASK_STACK_BUILDER);
|
||||||
|
dialogInterface.dismiss();
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.ask_to_launch_cancel, (dialogInterface, i) -> {
|
||||||
|
dialogInterface.dismiss();
|
||||||
|
})
|
||||||
|
.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TestBase setupTaskPreset() {
|
||||||
|
Node mRoot = Node.newRootNode();
|
||||||
|
// Describe initial setup of tasks
|
||||||
|
// create singleTask, singleInstance, and two documents in separate tasks
|
||||||
|
Node singleTask = newTaskNode()
|
||||||
|
.addChild(new Node(new ComponentName(this, SingleTaskActivity.class)));
|
||||||
|
Node docLaunchAlways = newTaskNode()
|
||||||
|
.addChild(new Node(new ComponentName(this, DocumentLaunchAlwaysActivity.class)));
|
||||||
|
Node docLaunchInto = newTaskNode()
|
||||||
|
.addChild(new Node(new ComponentName(this, DocumentLaunchIntoActivity.class)));
|
||||||
|
// Create three t0asks with three activities each, with affinity set
|
||||||
|
Node taskAffinity1 = newTaskNode()
|
||||||
|
.addChild(new Node(new ComponentName(this, TaskAffinity1Activity.class)))
|
||||||
|
.addChild(new Node(new ComponentName(this, TaskAffinity1Activity.class)))
|
||||||
|
.addChild(new Node(new ComponentName(this, TaskAffinity1Activity.class)));
|
||||||
|
Node taskAffinity2 = newTaskNode()
|
||||||
|
.addChild(new Node(new ComponentName(this, TaskAffinity2Activity.class)))
|
||||||
|
.addChild(new Node(new ComponentName(this, TaskAffinity2Activity.class)))
|
||||||
|
.addChild(new Node(new ComponentName(this, TaskAffinity2Activity.class)));
|
||||||
|
Node taskAffinity3 = newTaskNode()
|
||||||
|
.addChild(new Node(new ComponentName(this, TaskAffinity3Activity.class)))
|
||||||
|
.addChild(new Node(new ComponentName(this, TaskAffinity3Activity.class)))
|
||||||
|
.addChild(new Node(new ComponentName(this, TaskAffinity3Activity.class)));
|
||||||
|
mRoot.addChild(singleTask).addChild(docLaunchAlways).addChild(docLaunchInto)
|
||||||
|
.addChild(taskAffinity1).addChild(taskAffinity2).addChild(taskAffinity3);
|
||||||
|
return new TestBase(this, mRoot);
|
||||||
|
}
|
||||||
|
|
||||||
protected void runIntentTests() {
|
protected void runIntentTests() {
|
||||||
startActivity(getPackageManager()
|
startActivity(getPackageManager()
|
||||||
.getLaunchIntentForPackage("com.example.android.intentplayground.test"));
|
.getLaunchIntentForPackage("com.example.android.intentplayground.test"));
|
||||||
@@ -160,7 +217,7 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
|||||||
R.id.build_intent_view);
|
R.id.build_intent_view);
|
||||||
demo.addStep(R.string.help_step_four, R.id.fragment_container_bottom,
|
demo.addStep(R.string.help_step_four, R.id.fragment_container_bottom,
|
||||||
R.id.launch_button);
|
R.id.launch_button);
|
||||||
demo.setScroller((ScrollView) findViewById(R.id.scroll_container));
|
demo.setScroller(findViewById(R.id.scroll_container));
|
||||||
demo.setOnFinish(() -> container.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE));
|
demo.setOnFinish(() -> container.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE));
|
||||||
fragmentManager.beginTransaction()
|
fragmentManager.beginTransaction()
|
||||||
.add(R.id.root_container, demo)
|
.add(R.id.root_container, demo)
|
||||||
|
|||||||
@@ -58,134 +58,6 @@ public class LauncherActivity extends BaseActivity {
|
|||||||
mFirstLaunch = true;
|
mFirstLaunch = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up a hierarchy of{@link Node}s that represents the desired initial task stack.
|
|
||||||
*/
|
|
||||||
protected void setupTaskPreset() {
|
|
||||||
Node mRoot = Node.newRootNode();
|
|
||||||
// Describe initial setup of tasks
|
|
||||||
// create singleTask, singleInstance, and two documents in separate tasks
|
|
||||||
Node singleTask = newTaskNode()
|
|
||||||
.addChild( new Node(new ComponentName(this, SingleTaskActivity.class)));
|
|
||||||
Node docLaunchAlways = newTaskNode()
|
|
||||||
.addChild( new Node(new ComponentName(this, DocumentLaunchAlwaysActivity.class)));
|
|
||||||
Node docLaunchInto = newTaskNode()
|
|
||||||
.addChild( new Node(new ComponentName(this, DocumentLaunchIntoActivity.class)));
|
|
||||||
// Create three t0asks with three activities each, with affinity set
|
|
||||||
Node taskAffinity1 = newTaskNode()
|
|
||||||
.addChild(new Node(new ComponentName(this, TaskAffinity1Activity.class)))
|
|
||||||
.addChild(new Node(new ComponentName(this, TaskAffinity1Activity.class)))
|
|
||||||
.addChild(new Node(new ComponentName(this, TaskAffinity1Activity.class)));
|
|
||||||
Node taskAffinity2 = newTaskNode()
|
|
||||||
.addChild(new Node(new ComponentName(this, TaskAffinity2Activity.class)))
|
|
||||||
.addChild(new Node(new ComponentName(this, TaskAffinity2Activity.class)))
|
|
||||||
.addChild(new Node(new ComponentName(this, TaskAffinity2Activity.class)));
|
|
||||||
Node taskAffinity3 = newTaskNode()
|
|
||||||
.addChild(new Node(new ComponentName(this, TaskAffinity3Activity.class)))
|
|
||||||
.addChild(new Node(new ComponentName(this, TaskAffinity3Activity.class)))
|
|
||||||
.addChild(new Node(new ComponentName(this, TaskAffinity3Activity.class)));
|
|
||||||
mRoot.addChild(singleTask).addChild(docLaunchAlways).addChild(docLaunchInto)
|
|
||||||
.addChild(taskAffinity1).addChild(taskAffinity2).addChild(taskAffinity3);
|
|
||||||
mTester = new TestBase(this, mRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void loadMode(Mode mode) {
|
|
||||||
if (mode == Mode.LAUNCH) {
|
|
||||||
super.loadMode(mode);
|
|
||||||
long nonEmptyTasks = 0;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
nonEmptyTasks = getSystemService(ActivityManager.class)
|
|
||||||
.getAppTasks().stream().filter(t -> TaskInfo.getActivities(t.getTaskInfo()).size() != 0)
|
|
||||||
.count();
|
|
||||||
}
|
|
||||||
if (nonEmptyTasks <= 1 && !mDontLaunch) askToLaunchTasks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
if (!mFirstLaunch && mSnackBarIsVisible) {
|
|
||||||
hideSnackBar();
|
|
||||||
} else {
|
|
||||||
mFirstLaunch = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void askToLaunchTasks() {
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
|
||||||
.setMessage(R.string.launch_explanation)
|
|
||||||
.setTitle(R.string.ask_to_launch)
|
|
||||||
.setPositiveButton(R.string.ask_to_launch_affirm, (dialogInterface, i) -> {
|
|
||||||
showSnackBar(() -> {
|
|
||||||
mTester.startActivities(TestBase.LaunchStyle.TASK_STACK_BUILDER);
|
|
||||||
});
|
|
||||||
dialogInterface.dismiss();
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.ask_to_launch_cancel, (dialogInterface, i) -> {
|
|
||||||
dialogInterface.dismiss();
|
|
||||||
mDontLaunch = true;
|
|
||||||
hideSnackBar();
|
|
||||||
})
|
|
||||||
.create();
|
|
||||||
startSnackBar();
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares the custom snackbar window. We must use an application overlay because normal
|
|
||||||
* toasts and snackbars disappear once TestBase.startActivities() starts.
|
|
||||||
*/
|
|
||||||
private void startSnackBar() {
|
|
||||||
WindowManager wm = getSystemService(WindowManager.class);
|
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
|
||||||
FrameLayout frame = new FrameLayout(this);
|
|
||||||
inflater.inflate(R.layout.snack_loading, frame, true /* attachToRoot */);
|
|
||||||
mSnackBarRootView = frame;
|
|
||||||
if (!requestOverlayPermission()) return;
|
|
||||||
wm.addView(frame, makeLayoutParams(TYPE_APPLICATION_OVERLAY));
|
|
||||||
mSnackBarIsVisible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean requestOverlayPermission() {
|
|
||||||
if (Settings.canDrawOverlays(this)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// Start manage overlays activity
|
|
||||||
Intent intent = new Intent().setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
|
|
||||||
.setData(Uri.fromParts(getString(R.string.package_uri_scheme),
|
|
||||||
getPackageName(), null /* fragment */));
|
|
||||||
startActivity(intent);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showSnackBar(Runnable onShow) {
|
|
||||||
TextView tv = mSnackBarRootView.findViewById(R.id.snackbar_text);
|
|
||||||
tv.setText(getString(R.string.launch_wait_toast));
|
|
||||||
mSnackBarRootView.postDelayed(onShow, SNACKBAR_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideSnackBar() {
|
|
||||||
if (mSnackBarIsVisible) {
|
|
||||||
WindowManager wm = getSystemService(WindowManager.class);
|
|
||||||
mSnackBarIsVisible = false;
|
|
||||||
wm.removeView(mSnackBarRootView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private LayoutParams makeLayoutParams(int type) {
|
|
||||||
LayoutParams params = new LayoutParams(type, FLAG_NOT_TOUCH_MODAL);
|
|
||||||
params.format = PixelFormat.TRANSLUCENT;
|
|
||||||
params.width = MATCH_PARENT;
|
|
||||||
params.height = WRAP_CONTENT;
|
|
||||||
params.gravity = Gravity.getAbsoluteGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
|
|
||||||
View.LAYOUT_DIRECTION_RTL);
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches activity with the selected options.
|
* Launches activity with the selected options.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user