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:
Liam Clark
2018-11-26 14:25:57 -08:00
parent a0365534d3
commit a2f0b5d456
5 changed files with 67 additions and 135 deletions

View File

@@ -18,8 +18,6 @@
package="com.example.android.intentplayground">
<!-- Used to reorder tasks using android.app.ActivityManager.moveTaskToFront() -->
<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
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

View File

@@ -16,6 +16,10 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/app_bar_launch"
android:title="@string/launch_default_activities"/>
<item
android:id="@+id/app_bar_test"
android:icon="@drawable/icon_tests"

View File

@@ -88,4 +88,5 @@
<string name="plural_activities_text">%d activities</string>
<string name="new_task">NEW</string>
<string name="run_intent_tests">Run Intent tests</string>
<string name="launch_default_activities">Launch Default Activities</string>
</resources>

View File

@@ -16,20 +16,25 @@
package com.example.android.intentplayground;
import static com.example.android.intentplayground.Node.newTaskNode;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import java.util.ArrayList;
/**
@@ -42,7 +47,9 @@ public abstract class BaseActivity extends AppCompatActivity implements
public static final String TREE_FRAGMENT = "com.example.android.treeFragment";
public static final String EXPECTED_TREE_FRAGMENT = "com.example.android.expectedTreeFragment";
public static final int LAUNCH_REQUEST_CODE = 0xEF;
public enum Mode {LAUNCH, VERIFY, RESULT}
public boolean userLeaveHintWasCalled = false;
protected Mode mStatus = Mode.LAUNCH;
@@ -53,7 +60,9 @@ public abstract class BaseActivity extends AppCompatActivity implements
if (BuildConfig.DEBUG) Log.d(getLocalClassName(), "onCreate()");
// Setup action bar
Toolbar appBar = findViewById(R.id.app_bar);
appBar.setTitle(this.getClass().getSimpleName());
setSupportActionBar(appBar);
loadMode(Mode.LAUNCH);
}
@@ -68,6 +77,7 @@ public abstract class BaseActivity extends AppCompatActivity implements
/**
* Initializes the UI for the specified {@link Mode}.
*
* @param mode The mode to display.
*/
protected void loadMode(Mode mode) {
@@ -129,10 +139,57 @@ public abstract class BaseActivity extends AppCompatActivity implements
case R.id.app_bar_test:
runIntentTests();
break;
case R.id.app_bar_launch:
askToLaunchTasks();
break;
}
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() {
startActivity(getPackageManager()
.getLaunchIntentForPackage("com.example.android.intentplayground.test"));
@@ -160,7 +217,7 @@ public abstract class BaseActivity extends AppCompatActivity implements
R.id.build_intent_view);
demo.addStep(R.string.help_step_four, R.id.fragment_container_bottom,
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));
fragmentManager.beginTransaction()
.add(R.id.root_container, demo)

View File

@@ -58,134 +58,6 @@ public class LauncherActivity extends BaseActivity {
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.
*/