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