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.Shared.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 android.support.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) Log.d(TAG, "Starting new task > " + intent.getData());
107         startActivity(intent);
108     }
109 
restoreTask(Intent intent)110     private boolean restoreTask(Intent intent) {
111         if (DEBUG) Log.d(TAG, "Restoring existing task > " + intent.getData());
112         try {
113             // TODO: This doesn't appear to restore a task once it has stopped running.
114             startActivity(intent);
115 
116             return true;
117         } catch (Exception e) {
118             Log.w(TAG, "Failed to restore task > " + intent.getData() +
119                     ". Clear all existing tasks and start a new one.", e);
120         }
121 
122         return false;
123     }
124 
clearTask(ActivityManager activities)125     private void clearTask(ActivityManager activities) {
126         List<AppTask> tasks = activities.getAppTasks();
127         for (AppTask task : tasks) {
128             task.finishAndRemoveTask();
129         }
130     }
131 
createLaunchIntent(Activity activity)132     public static final Intent createLaunchIntent(Activity activity) {
133         Intent intent = new Intent(activity, FilesActivity.class);
134         intent.setData(buildLaunchUri());
135 
136         // Relay any config overrides bits present in the original intent.
137         Intent original = activity.getIntent();
138         if (original != null) {
139             copyExtras(original, intent);
140             if (original.hasExtra(Intent.EXTRA_TITLE)) {
141                 intent.putExtra(
142                         Intent.EXTRA_TITLE,
143                         original.getStringExtra(Intent.EXTRA_TITLE));
144             }
145         }
146         return intent;
147     }
148 
copyExtras(Intent src, Intent dest)149     private static void copyExtras(Intent src, Intent dest) {
150         for (String extra : PERSISTENT_BOOLEAN_EXTRAS) {
151             if (src.hasExtra(extra)) {
152                 dest.putExtra(extra, src.getBooleanExtra(extra, false));
153             }
154         }
155     }
156 
buildLaunchUri()157     private static Uri buildLaunchUri() {
158         return new Uri.Builder()
159                 .authority(LAUNCH_CONTROL_AUTHORITY)
160                 .fragment(String.valueOf(System.currentTimeMillis()))
161                 .build();
162     }
163 
isLaunchUri(@ullable Uri uri)164     public static boolean isLaunchUri(@Nullable Uri uri) {
165         boolean result = uri != null && LAUNCH_CONTROL_AUTHORITY.equals(uri.getAuthority());
166         return result;
167     }
168 }
169