Home | History | Annotate | Download | only in intentplayground
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.intentplayground;
     18 
     19 import static com.example.android.intentplayground.Node.newTaskNode;
     20 
     21 import android.app.Activity;
     22 import android.content.ComponentName;
     23 import android.content.Intent;
     24 import android.os.Bundle;
     25 import com.google.android.material.floatingactionbutton.FloatingActionButton;
     26 import android.util.Log;
     27 import android.view.Menu;
     28 import android.view.MenuItem;
     29 
     30 import androidx.appcompat.app.AlertDialog;
     31 import androidx.appcompat.app.AppCompatActivity;
     32 import androidx.appcompat.widget.Toolbar;
     33 import androidx.fragment.app.FragmentManager;
     34 import androidx.fragment.app.FragmentTransaction;
     35 import androidx.lifecycle.ViewModelProvider;
     36 
     37 import com.example.android.intentplayground.Tracking.Tracker;
     38 
     39 import java.util.ArrayList;
     40 import java.util.List;
     41 import java.util.function.Consumer;
     42 
     43 /**
     44  * Implements the shared functionality for all of the other activities.
     45  */
     46 public abstract class BaseActivity extends AppCompatActivity implements
     47         IntentBuilderView.OnLaunchCallback {
     48     public final static String EXTRA_LAUNCH_FORWARD = "com.example.android.launchForward";
     49     public final static String BUILDER_VIEW = "com.example.android.builderFragment";
     50     public static final String TREE_FRAGMENT = "com.example.android.treeFragment";
     51     public static final String EXPECTED_TREE_FRAGMENT = "com.example.android.expectedTreeFragment";
     52     public static final int LAUNCH_REQUEST_CODE = 0xEF;
     53     private static final int LAUNCH_FOR_RESULT_ID = 1;
     54 
     55     public enum Mode {LAUNCH, VERIFY, RESULT}
     56 
     57     public boolean userLeaveHintWasCalled = false;
     58     protected Mode mStatus = Mode.LAUNCH;
     59 
     60     /**
     61      * To display the task / activity overview in {@link TreeFragment} we track onResume and
     62      * onDestroy calls in this global location. {@link BaseActivity} should delegate to
     63      * {@link Tracker#onResume(Activity)} and {@link Tracker#onDestroy(Activity)} in it's respective
     64      * lifecycle callbacks.
     65      */
     66     private static Tracker mTracker = new Tracker();
     67 
     68     @Override
     69     protected void onCreate(Bundle savedInstanceState) {
     70         super.onCreate(savedInstanceState);
     71         setContentView(R.layout.activity_main);
     72         if (BuildConfig.DEBUG) Log.d(getLocalClassName(), "onCreate()");
     73         // Setup action bar
     74         Toolbar appBar = findViewById(R.id.app_bar);
     75         appBar.setTitle(this.getClass().getSimpleName());
     76         setSupportActionBar(appBar);
     77 
     78         FloatingActionButton launchButton = findViewById(R.id.launch_fab);
     79         launchButton.setOnClickListener(l -> {
     80             LaunchFragment fragment = new LaunchFragment();
     81 
     82             getSupportFragmentManager().beginTransaction()
     83                     .addToBackStack(null)
     84                     .replace(R.id.fragment_container, fragment)
     85                     .commit();
     86         });
     87 
     88         BaseActivityViewModel viewModel = (new ViewModelProvider(this,
     89                 new ViewModelProvider.NewInstanceFactory())).get(BaseActivityViewModel.class);
     90 
     91         viewModel.getFabActions().observe(this, action -> {
     92             switch (action) {
     93                 case Show:
     94                     launchButton.show();
     95                     break;
     96                 case Hide:
     97                     launchButton.hide();
     98                     break;
     99             }
    100         });
    101 
    102 
    103         loadMode(Mode.LAUNCH);
    104     }
    105 
    106     @Override
    107     protected void onResume() {
    108         super.onResume();
    109         mTracker.onResume(this);
    110         Intent launchForward = prepareLaunchForward();
    111         if (launchForward != null) {
    112             startActivity(launchForward);
    113         }
    114     }
    115 
    116     @Override
    117     protected  void onDestroy() {
    118         super.onDestroy();
    119         mTracker.onDestroy(this);
    120     }
    121 
    122     static void addTrackerListener(Consumer<List<Tracking.Task>> listener) {
    123         mTracker.addListener(listener);
    124     }
    125 
    126     static void removeTrackerListener(Consumer<List<Tracking.Task>> listener) {
    127         mTracker.removeListener(listener);
    128     }
    129 
    130     /**
    131      * Initializes the UI for the specified {@link Mode}.
    132      *
    133      * @param mode The mode to display.
    134      */
    135     protected void loadMode(Mode mode) {
    136         FragmentManager fragmentManager = getSupportFragmentManager();
    137 
    138         if (fragmentManager.findFragmentById(R.id.fragment_container) == null) {
    139             FragmentTransaction transaction = fragmentManager.beginTransaction()
    140                     .setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
    141             if (mode == Mode.LAUNCH) {
    142                 TreeFragment currentTaskFragment = new TreeFragment();
    143                 Bundle args = new Bundle();
    144                 args.putString(TreeFragment.FRAGMENT_TITLE,
    145                         getString(R.string.current_task_hierarchy_title));
    146                 currentTaskFragment.setArguments(args);
    147                 transaction.add(R.id.fragment_container, currentTaskFragment, TREE_FRAGMENT);
    148                 transaction.add(R.id.fragment_container, new IntentFragment());
    149                 transaction.commit();
    150 
    151                 mStatus = Mode.LAUNCH;
    152             }
    153         }
    154     }
    155 
    156     /**
    157      * Launches activity with the selected options.
    158      */
    159     @Override
    160     public void launchActivity(Intent intent, boolean forResult) {
    161         if (forResult) {
    162             startActivityForResult(intent, LAUNCH_FOR_RESULT_ID);
    163         } else {
    164             startActivity(intent);
    165         }
    166 
    167         // If people press back we want them to see the overview rather than the launch fragment.
    168         // To achieve this we pop the launchFragment from the stack when we go to the next activity.
    169         getSupportFragmentManager().popBackStack();
    170     }
    171 
    172     @Override
    173     protected void onNewIntent(Intent intent) {
    174         super.onNewIntent(intent);
    175         setIntent(intent);
    176     }
    177 
    178     @Override
    179     public boolean onCreateOptionsMenu(Menu menu) {
    180         getMenuInflater().inflate(R.menu.app_bar, menu);
    181         return true;
    182     }
    183 
    184     @Override
    185     public boolean onOptionsItemSelected(MenuItem item) {
    186         switch (item.getItemId()) {
    187             case R.id.app_bar_test:
    188                 runIntentTests();
    189                 break;
    190             case R.id.app_bar_launch_default:
    191                 askToLaunchTasks();
    192                 break;
    193         }
    194         return super.onOptionsItemSelected(item);
    195     }
    196 
    197     private void askToLaunchTasks() {
    198         AlertDialog dialog = new AlertDialog.Builder(this)
    199                 .setMessage(R.string.launch_explanation)
    200                 .setTitle(R.string.ask_to_launch)
    201                 .setPositiveButton(R.string.ask_to_launch_affirm, (dialogInterface, i) -> {
    202                     setupTaskPreset().startActivities(TestBase.LaunchStyle.TASK_STACK_BUILDER);
    203                     dialogInterface.dismiss();
    204                 })
    205                 .setNegativeButton(R.string.ask_to_launch_cancel, (dialogInterface, i) -> {
    206                     dialogInterface.dismiss();
    207                 })
    208                 .create();
    209         dialog.show();
    210     }
    211 
    212     protected TestBase setupTaskPreset() {
    213         Node mRoot = Node.newRootNode();
    214         // Describe initial setup of tasks
    215         // create singleTask, singleInstance, and two documents in separate tasks
    216         Node singleTask = newTaskNode()
    217                 .addChild(new Node(new ComponentName(this, SingleTaskActivity.class)));
    218         Node docLaunchAlways = newTaskNode()
    219                 .addChild(new Node(new ComponentName(this, DocumentLaunchAlwaysActivity.class)));
    220         Node docLaunchInto = newTaskNode()
    221                 .addChild(new Node(new ComponentName(this, DocumentLaunchIntoActivity.class)));
    222         // Create three t0asks with three activities each, with affinity set
    223         Node taskAffinity1 = newTaskNode()
    224                 .addChild(new Node(new ComponentName(this, TaskAffinity1Activity.class)))
    225                 .addChild(new Node(new ComponentName(this, TaskAffinity1Activity.class)))
    226                 .addChild(new Node(new ComponentName(this, TaskAffinity1Activity.class)));
    227         Node taskAffinity2 = newTaskNode()
    228                 .addChild(new Node(new ComponentName(this, TaskAffinity2Activity.class)))
    229                 .addChild(new Node(new ComponentName(this, TaskAffinity2Activity.class)))
    230                 .addChild(new Node(new ComponentName(this, TaskAffinity2Activity.class)));
    231         Node taskAffinity3 = newTaskNode()
    232                 .addChild(new Node(new ComponentName(this, TaskAffinity3Activity.class)))
    233                 .addChild(new Node(new ComponentName(this, TaskAffinity3Activity.class)))
    234                 .addChild(new Node(new ComponentName(this, TaskAffinity3Activity.class)));
    235         mRoot.addChild(singleTask).addChild(docLaunchAlways).addChild(docLaunchInto)
    236                 .addChild(taskAffinity1).addChild(taskAffinity2).addChild(taskAffinity3);
    237         return new TestBase(this, mRoot);
    238     }
    239 
    240     protected void runIntentTests() {
    241         startActivity(getPackageManager()
    242                 .getLaunchIntentForPackage("com.example.android.intentplayground.test"));
    243     }
    244 
    245     protected Intent prepareLaunchForward() {
    246         Intent intent = getIntent();
    247         Intent nextIntent = null;
    248         if (intent.hasExtra(EXTRA_LAUNCH_FORWARD)) {
    249             Log.e(getLocalClassName(), "It's happening! LAUNCH_FORWARD");
    250             ArrayList<Intent> intents = intent.getParcelableArrayListExtra(EXTRA_LAUNCH_FORWARD);
    251             if (!intents.isEmpty()) {
    252                 nextIntent = intents.remove(0);
    253                 nextIntent.putParcelableArrayListExtra(EXTRA_LAUNCH_FORWARD, intents);
    254                 if (BuildConfig.DEBUG) {
    255                     Log.d(getLocalClassName(), EXTRA_LAUNCH_FORWARD + " "
    256                             + nextIntent.getComponent().toString());
    257                 }
    258             }
    259         }
    260         return nextIntent;
    261     }
    262 
    263     /**
    264      * Sets a public field for the purpose of testing.
    265      */
    266     @Override
    267     protected void onUserLeaveHint() {
    268         super.onUserLeaveHint();
    269         userLeaveHintWasCalled = true;
    270     }
    271 }
    272