| /* |
| * Copyright (C) 2017 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 static com.example.android.intentplayground.Node.newTaskNode; |
| |
| import android.app.Activity; |
| import android.content.ComponentName; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import com.google.android.material.floatingactionbutton.FloatingActionButton; |
| import android.util.Log; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| |
| import android.widget.Toast; |
| |
| 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 androidx.lifecycle.ViewModelProvider; |
| |
| import com.example.android.intentplayground.Tracking.Tracker; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.function.Consumer; |
| |
| /** |
| * Implements the shared functionality for all of the other activities. |
| */ |
| 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 EXPECTED_TREE_FRAGMENT = "com.example.android.expectedTreeFragment"; |
| public static final int LAUNCH_REQUEST_CODE = 0xEF; |
| private static final int LAUNCH_FOR_RESULT_ID = 1; |
| |
| public enum Mode {LAUNCH, VERIFY, RESULT} |
| |
| public boolean userLeaveHintWasCalled = false; |
| protected Mode mStatus = Mode.LAUNCH; |
| |
| /** |
| * To display the task / activity overview in {@link TreeFragment} we track onResume and |
| * onDestroy calls in this global location. {@link BaseActivity} should delegate to |
| * {@link Tracker#onResume(Activity)} and {@link Tracker#onDestroy(Activity)} in it's respective |
| * lifecycle callbacks. |
| */ |
| private static Tracker mTracker = new Tracker(); |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setContentView(R.layout.activity_main); |
| if (BuildConfig.DEBUG) Log.d(getLocalClassName(), "onCreate()"); |
| // Setup action bar |
| Toolbar appBar = findViewById(R.id.app_bar); |
| appBar.setTitle(this.getClass().getSimpleName()); |
| setSupportActionBar(appBar); |
| |
| FloatingActionButton launchButton = findViewById(R.id.launch_fab); |
| launchButton.setOnClickListener(l -> { |
| LaunchFragment fragment = new LaunchFragment(); |
| |
| getSupportFragmentManager().beginTransaction() |
| .addToBackStack(null) |
| .replace(R.id.fragment_container, fragment) |
| .commit(); |
| }); |
| |
| BaseActivityViewModel viewModel = (new ViewModelProvider(this, |
| new ViewModelProvider.NewInstanceFactory())).get(BaseActivityViewModel.class); |
| |
| viewModel.getFabActions().observe(this, action -> { |
| switch (action) { |
| case Show: |
| launchButton.show(); |
| break; |
| case Hide: |
| launchButton.hide(); |
| break; |
| } |
| }); |
| |
| |
| loadMode(Mode.LAUNCH); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| mTracker.onResume(this); |
| Intent launchForward = prepareLaunchForward(); |
| if (launchForward != null) { |
| startActivity(launchForward); |
| } |
| } |
| |
| @Override |
| protected void onDestroy() { |
| super.onDestroy(); |
| mTracker.onDestroy(this); |
| } |
| |
| static void addTrackerListener(Consumer<List<Tracking.Task>> listener) { |
| mTracker.addListener(listener); |
| } |
| |
| static void removeTrackerListener(Consumer<List<Tracking.Task>> listener) { |
| mTracker.removeListener(listener); |
| } |
| |
| /** |
| * Initializes the UI for the specified {@link Mode}. |
| * |
| * @param mode The mode to display. |
| */ |
| protected void loadMode(Mode mode) { |
| FragmentManager fragmentManager = getSupportFragmentManager(); |
| |
| if (fragmentManager.findFragmentById(R.id.fragment_container) == null) { |
| FragmentTransaction transaction = fragmentManager.beginTransaction() |
| .setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out); |
| if (mode == Mode.LAUNCH) { |
| TreeFragment currentTaskFragment = new TreeFragment(); |
| Bundle args = new Bundle(); |
| args.putString(TreeFragment.FRAGMENT_TITLE, |
| getString(R.string.current_task_hierarchy_title)); |
| currentTaskFragment.setArguments(args); |
| transaction.add(R.id.fragment_container, currentTaskFragment, TREE_FRAGMENT); |
| transaction.add(R.id.fragment_container, new IntentFragment()); |
| transaction.commit(); |
| |
| mStatus = Mode.LAUNCH; |
| } |
| } |
| } |
| |
| /** |
| * Launches activity with the selected options. |
| */ |
| @Override |
| public void launchActivity(Intent intent, boolean forResult) { |
| if (forResult) { |
| startActivityForResult(intent, LAUNCH_FOR_RESULT_ID); |
| } else { |
| startActivity(intent); |
| } |
| |
| // If people press back we want them to see the overview rather than the launch fragment. |
| // To achieve this we pop the launchFragment from the stack when we go to the next activity. |
| getSupportFragmentManager().popBackStack(); |
| } |
| |
| @Override |
| protected void onNewIntent(Intent intent) { |
| super.onNewIntent(intent); |
| setIntent(intent); |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| getMenuInflater().inflate(R.menu.app_bar, menu); |
| return true; |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.app_bar_test: |
| runIntentTests(); |
| break; |
| case R.id.app_bar_launch_default: |
| 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() { |
| final Intent intent = getPackageManager() |
| .getLaunchIntentForPackage("com.example.android.intentplayground.test"); |
| if (intent != null) { |
| startActivity(intent); |
| } else { |
| Toast.makeText(this, |
| R.string.launch_testing_activities_failed, Toast.LENGTH_LONG).show(); |
| } |
| } |
| |
| protected Intent prepareLaunchForward() { |
| Intent intent = getIntent(); |
| Intent nextIntent = null; |
| if (intent.hasExtra(EXTRA_LAUNCH_FORWARD)) { |
| Log.e(getLocalClassName(), "It's happening! LAUNCH_FORWARD"); |
| ArrayList<Intent> intents = intent.getParcelableArrayListExtra(EXTRA_LAUNCH_FORWARD); |
| if (!intents.isEmpty()) { |
| nextIntent = intents.remove(0); |
| nextIntent.putParcelableArrayListExtra(EXTRA_LAUNCH_FORWARD, intents); |
| if (BuildConfig.DEBUG) { |
| Log.d(getLocalClassName(), EXTRA_LAUNCH_FORWARD + " " |
| + nextIntent.getComponent().toString()); |
| } |
| } |
| } |
| return nextIntent; |
| } |
| |
| /** |
| * Sets a public field for the purpose of testing. |
| */ |
| @Override |
| protected void onUserLeaveHint() { |
| super.onUserLeaveHint(); |
| userLeaveHintWasCalled = true; |
| } |
| } |