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