MultiDisplay launcher sample
Demo launcher that allows starting activities on secondary displays. Test: Manual Change-Id: I9d6652b81fc928767d47f94a6ac5bc9de0fb8f37
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user