1 /*
2  * Copyright (C) 2015 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.android.documentsui.files;
18 
19 import static com.android.documentsui.base.SharedMinimal.DEBUG;
20 
21 import android.app.Activity;
22 import android.app.ActivityManager;
23 import android.app.ActivityManager.AppTask;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.provider.DocumentsContract;
29 import androidx.annotation.Nullable;
30 import android.util.Log;
31 
32 import com.android.documentsui.R;
33 
34 import java.util.List;
35 
36 /**
37  * Provides FilesActivity task grouping support. This allows multiple FilesActivities to be
38  * launched (a behavior imparted by way of {@code documentLaunchMode="intoExisting"} and
39  * our use of pseudo document {@link Uri}s. This also lets us move an existing task
40  * to the foreground when a suitable task exists.
41  *
42  * Requires that {@code documentLaunchMode="intoExisting"} be set on target activity.
43  *
44  */
45 public class LauncherActivity extends Activity {
46 
47     public static final String TASK_LABEL_RES = "com.android.documentsui.taskLabel";
48     public static final String TASK_ICON_RES = "com.android.documentsui.taskIcon";
49 
50     private static final String LAUNCH_CONTROL_AUTHORITY = "com.android.documentsui.launchControl";
51     private static final String TAG = "LauncherActivity";
52 
53     // Array of boolean extras that should be copied when creating new launch intents.
54     // Missing intents will be ignored.
55     private static final String[] PERSISTENT_BOOLEAN_EXTRAS = {
56         DocumentsContract.EXTRA_SHOW_ADVANCED
57     };
58 
59     @Override
onCreate(Bundle savedInstanceState)60     protected void onCreate(Bundle savedInstanceState) {
61         super.onCreate(savedInstanceState);
62 
63         launch();
64 
65         finish();
66     }
67 
launch()68     private void launch() {
69         ActivityManager activities = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
70 
71         Intent intent = findTask(activities);
72         if (intent != null) {
73             if (restoreTask(intent)) {
74                 return;
75             } else {
76                 // We failed to restore the task. It may happen when system was just updated and we
77                 // moved the location of the targeted activity. Chances is that the rest of tasks
78                 // can't be restored either, so clean those tasks and start a new one.
79                 clearTask(activities);
80             }
81         }
82 
83         startTask();
84     }
85 
findTask(ActivityManager activities)86     private @Nullable Intent findTask(ActivityManager activities) {
87         List<AppTask> tasks = activities.getAppTasks();
88         for (AppTask task : tasks) {
89             Intent intent = task.getTaskInfo().baseIntent;
90             Uri uri = intent.getData();
91             if (isLaunchUri(uri)) {
92                 return intent;
93             }
94         }
95         return null;
96     }
97 
startTask()98     private void startTask() {
99         Intent intent = createLaunchIntent(this);
100 
101         intent.putExtra(TASK_LABEL_RES, R.string.launcher_label);
102         intent.putExtra(TASK_ICON_RES, R.drawable.launcher_icon);
103 
104         // Forward any flags from the original intent.
105         intent.setFlags(getIntent().getFlags());
106         if (DEBUG) {
107             Log.d(TAG, "Starting new task > " + intent.getData());
108         }
109         startActivity(intent);
110     }
111 
restoreTask(Intent intent)112     private boolean restoreTask(Intent intent) {
113         if (DEBUG) {
114             Log.d(TAG, "Restoring existing task > " + intent.getData());
115         }
116         try {
117             // TODO: This doesn't appear to restore a task once it has stopped running.
118             startActivity(intent);
119 
120             return true;
121         } catch (Exception e) {
122             Log.w(TAG, "Failed to restore task > " + intent.getData() +
123                     ". Clear all existing tasks and start a new one.", e);
124         }
125 
126         return false;
127     }
128 
clearTask(ActivityManager activities)129     private void clearTask(ActivityManager activities) {
130         List<AppTask> tasks = activities.getAppTasks();
131         for (AppTask task : tasks) {
132             task.finishAndRemoveTask();
133         }
134     }
135 
createLaunchIntent(Activity activity)136     public static final Intent createLaunchIntent(Activity activity) {
137         Intent intent = new Intent(activity, FilesActivity.class);
138         intent.setData(buildLaunchUri());
139 
140         // Relay any config overrides bits present in the original intent.
141         Intent original = activity.getIntent();
142         if (original != null) {
143             copyExtras(original, intent);
144             if (original.hasExtra(Intent.EXTRA_TITLE)) {
145                 intent.putExtra(
146                         Intent.EXTRA_TITLE,
147                         original.getStringExtra(Intent.EXTRA_TITLE));
148             }
149         }
150         return intent;
151     }
152 
copyExtras(Intent src, Intent dest)153     private static void copyExtras(Intent src, Intent dest) {
154         for (String extra : PERSISTENT_BOOLEAN_EXTRAS) {
155             if (src.hasExtra(extra)) {
156                 dest.putExtra(extra, src.getBooleanExtra(extra, false));
157             }
158         }
159     }
160 
buildLaunchUri()161     private static Uri buildLaunchUri() {
162         return new Uri.Builder()
163                 .authority(LAUNCH_CONTROL_AUTHORITY)
164                 .fragment(String.valueOf(System.currentTimeMillis()))
165                 .build();
166     }
167 
isLaunchUri(@ullable Uri uri)168     public static boolean isLaunchUri(@Nullable Uri uri) {
169         boolean result = uri != null && LAUNCH_CONTROL_AUTHORITY.equals(uri.getAuthority());
170         return result;
171     }
172 }
173