MultiDisplay launcher sample

Demo launcher that allows starting activities on secondary displays.

Test: Manual
Change-Id: I9d6652b81fc928767d47f94a6ac5bc9de0fb8f37
This commit is contained in:
Andrii Kulian
2018-06-29 18:21:50 -07:00
parent 5eb3a49fa8
commit 9048eea32c
11 changed files with 689 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multidisplay.launcher;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
/** An entry that represents a single activity that can be launched. */
public class AppEntry {
AppEntry(ResolveInfo info, PackageManager packageManager) {
mLabel = info.loadLabel(packageManager).toString();
mIcon = info.loadIcon(packageManager);
mLaunchIntent = new Intent();
mLaunchIntent.setComponent(new ComponentName(info.activityInfo.packageName,
info.activityInfo.name));
}
String getLabel() {
return mLabel;
}
Drawable getIcon() {
return mIcon;
}
Intent getLaunchIntent() { return mLaunchIntent; }
@Override
public String toString() {
return mLabel;
}
private String mLabel;
private Drawable mIcon;
private Intent mLaunchIntent;
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multidisplay.launcher;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.example.android.multidisplay.R;
import java.util.List;
/** Adapter for available apps list. */
public class AppListAdapter extends ArrayAdapter<AppEntry> {
private final LayoutInflater mInflater;
AppListAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
void setData(List<AppEntry> data) {
clear();
if (data != null) {
addAll(data);
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.app_grid_item, parent, false);
} else {
view = convertView;
}
AppEntry item = getItem(position);
((ImageView)view.findViewById(R.id.app_icon)).setImageDrawable(item.getIcon());
((TextView)view.findViewById(R.id.app_name)).setText(item.getLabel());
return view;
}
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multidisplay.launcher;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import java.util.ArrayList;
import java.util.List;
/**
* A loader that queries the {@link PackageManager} for a list of activities that can be launched.
*/
public class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {
private final PackageManager mPackageManager;
private List<AppEntry> mApps;
private PackageIntentReceiver mPackageObserver;
AppListLoader(Context context) {
super(context);
// Retrieve the package manager for later use; note we don't
// use 'context' directly but instead the save global application
// context returned by getContext().
mPackageManager = getContext().getPackageManager();
}
@Override
public List<AppEntry> loadInBackground() {
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent,
PackageManager.GET_META_DATA);
List<AppEntry> entries = new ArrayList<>();
if (apps != null) {
for (ResolveInfo app : apps) {
AppEntry entry = new AppEntry(app, mPackageManager);
entries.add(entry);
}
}
return entries;
}
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
@Override
public void deliverResult(List<AppEntry> apps) {
mApps = apps;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(apps);
}
}
/**
* Handles a request to start the Loader.
*/
@Override
protected void onStartLoading() {
if (mApps != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mApps);
}
// Start watching for changes in the app data.
if (mPackageObserver == null) {
mPackageObserver = new PackageIntentReceiver(this);
}
if (takeContentChanged() || mApps == null) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to completely reset the Loader.
*/
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
// At this point we can release the resources associated with 'apps'
// if needed.
if (mApps != null) {
mApps = null;
}
// Stop monitoring for changes.
if (mPackageObserver != null) {
getContext().unregisterReceiver(mPackageObserver);
mPackageObserver = null;
}
}
}

View File

@@ -0,0 +1,181 @@
/**
* Copyright (c) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multidisplay.launcher;
import static android.widget.Toast.LENGTH_LONG;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.Spinner;
import android.widget.Toast;
import com.example.android.multidisplay.R;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LauncherActivity extends Activity implements DisplayManager.DisplayListener,
LoaderManager.LoaderCallbacks<List<AppEntry>>{
private Spinner mDisplaySpinner;
private List<Display> mDisplayList;
private int mSelectedDisplayId;
private AppListAdapter mAppListAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDisplaySpinner = findViewById(R.id.spinner);
mDisplaySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
mSelectedDisplayId = mDisplayList.get(i).getDisplayId();
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
mSelectedDisplayId = -1;
}
});
final GridView appGridView = findViewById(R.id.app_grid);
mAppListAdapter = new AppListAdapter(this);
appGridView.setAdapter(mAppListAdapter);
final OnItemClickListener itemClickListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
final AppEntry entry = mAppListAdapter.getItem(position);
launch(entry.getLaunchIntent());
}
};
appGridView.setOnItemClickListener(itemClickListener);
getLoaderManager().initLoader(0, null, this);
}
@Override
protected void onResume() {
super.onResume();
updateDisplayList(null);
}
void launch(Intent launchIntent) {
if (mSelectedDisplayId == -1) {
Toast.makeText(this, R.string.select_display, LENGTH_LONG).show();
return;
}
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mSelectedDisplayId);
try {
startActivity(launchIntent, options.toBundle());
} catch (Exception e) {
final AlertDialog.Builder builder =
new AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert);
builder.setTitle(R.string.couldnt_launch)
.setMessage(e.getLocalizedMessage())
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
}
}
/**
* Read the list of currently connected displays and pick one.
* When the list changes it'll try to keep the previously selected display. If that one won't be
* available, it'll pick the display with biggest id (last connected).
*/
public void updateDisplayList(View view) {
final DisplayManager dm = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
mDisplayList = Arrays.asList(dm.getDisplays());
final List<String> spinnerItems = new ArrayList<>();
int preferredDisplayPosition = -1;
int biggestId = -1, biggestPos = -1;
for (int i = 0; i < mDisplayList.size(); i++) {
final Display display = mDisplayList.get(i);
final int id = display.getDisplayId();
final boolean isDisplayPrivate = (display.getFlags() & Display.FLAG_PRIVATE) != 0;
spinnerItems.add("" + id + ": " + display.getName()
+ (isDisplayPrivate ? " (private)" : ""));
if (id == mSelectedDisplayId) {
preferredDisplayPosition = i;
}
if (display.getDisplayId() > biggestId) {
biggestId = display.getDisplayId();
biggestPos = i;
}
}
if (preferredDisplayPosition == -1) {
preferredDisplayPosition = biggestPos;
}
mSelectedDisplayId = mDisplayList.get(preferredDisplayPosition).getDisplayId();
final ArrayAdapter<String> displayAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, spinnerItems);
displayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mDisplaySpinner.setAdapter(displayAdapter);
mDisplaySpinner.setSelection(preferredDisplayPosition);
}
@Override
public void onDisplayAdded(int displayId) {
updateDisplayList(null);
}
@Override
public void onDisplayRemoved(int displayId) {
updateDisplayList(null);
}
@Override
public void onDisplayChanged(int displayId) {
updateDisplayList(null);
}
@Override
public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
return new AppListLoader(this);
}
@Override
public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
mAppListAdapter.setData(data);
}
@Override
public void onLoaderReset(Loader<List<AppEntry>> loader) {
mAppListAdapter.setData(null);
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.multidisplay.launcher;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
public class PackageIntentReceiver extends BroadcastReceiver {
final AppListLoader mLoader;
public PackageIntentReceiver(AppListLoader loader) {
mLoader = loader;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mLoader.getContext().registerReceiver(this, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mLoader.getContext().registerReceiver(this, sdFilter);
}
@Override
public void onReceive(Context context, Intent intent) {
// Tell the loader about the change.
mLoader.onContentChanged();
}
}