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">
|
||||
<!-- 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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -39,10 +44,12 @@ public abstract class BaseActivity extends AppCompatActivity implements
|
||||
IntentBuilderView.OnLaunchCallback {
|
||||
public final static String EXTRA_LAUNCH_FORWARD = "com.example.android.launchForward";
|
||||
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 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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user