Update MD Launcher for large/external screens

- Added support for multiple launcher activity instances.
- Launcher now shows default wallpaper as background.
- The activity now expands behind system decor windows.
- App drawer can be invoked by clicking on FAB.
- Apps can be pinned on the screen.
- Wallpaper setting flow can be started from launcher.
- UI is optimized for large and xlarge screens.
- Launching a new instance is now optional.
- Other minor UI tweaks.

Bug: 116684201
Bug: 112452592
Bug: 112451761
Test: Manual
Change-Id: I52b5efa4362eab7fcaa5d23923329b68b1783847
This commit is contained in:
Andrii Kulian
2018-09-28 18:37:56 -07:00
parent d99497ab31
commit 91ca37c380
25 changed files with 996 additions and 281 deletions

View File

@@ -25,6 +25,10 @@ import android.graphics.drawable.Drawable;
/** An entry that represents a single activity that can be launched. */
public class AppEntry {
private String mLabel;
private Drawable mIcon;
private Intent mLaunchIntent;
AppEntry(ResolveInfo info, PackageManager packageManager) {
mLabel = info.loadLabel(packageManager).toString();
mIcon = info.loadIcon(packageManager);
@@ -43,12 +47,12 @@ public class AppEntry {
Intent getLaunchIntent() { return mLaunchIntent; }
ComponentName getComponentName() {
return mLaunchIntent.getComponent();
}
@Override
public String toString() {
return mLabel;
}
private String mLabel;
private Drawable mIcon;
private Intent mLaunchIntent;
}

View File

@@ -1,135 +0,0 @@
/**
* 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,125 @@
/**
* 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.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.ArrayList;
import java.util.List;
/**
* A view model that provides a list of activities that can be launched.
*/
public class AppListViewModel extends AndroidViewModel {
private final AppListLiveData mLiveData;
private final PackageIntentReceiver mPackageIntentReceiver;
public AppListViewModel(Application application) {
super(application);
mLiveData = new AppListLiveData(application);
mPackageIntentReceiver = new PackageIntentReceiver(mLiveData, application);
}
public LiveData<List<AppEntry>> getAppList() {
return mLiveData;
}
protected void onCleared() {
getApplication().unregisterReceiver(mPackageIntentReceiver);
}
}
class AppListLiveData extends LiveData<List<AppEntry>> {
private final PackageManager mPackageManager;
private int mCurrentDataVersion;
public AppListLiveData(Context context) {
mPackageManager = context.getPackageManager();
loadData();
}
void loadData() {
final int loadDataVersion = ++mCurrentDataVersion;
new AsyncTask<Void, Void, List<AppEntry>>() {
@Override
protected List<AppEntry> doInBackground(Void... voids) {
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;
}
@Override
protected void onPostExecute(List<AppEntry> data) {
if (mCurrentDataVersion == loadDataVersion) {
setValue(data);
}
}
}.execute();
}
}
/**
* Receiver used to notify live data about app list changes.
*/
class PackageIntentReceiver extends BroadcastReceiver {
private final AppListLiveData mLiveData;
public PackageIntentReceiver(AppListLiveData liveData, Context context) {
mLiveData = liveData;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
context.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);
context.registerReceiver(this, sdFilter);
}
@Override
public void onReceive(Context context, Intent intent) {
mLiveData.loadData();
}
}

View File

@@ -16,46 +16,87 @@
package com.example.android.multidisplay.launcher;
import static android.widget.Toast.LENGTH_LONG;
import static com.example.android.multidisplay.launcher.PinnedAppListViewModel.PINNED_APPS_KEY;
import android.app.Activity;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.app.LoaderManager;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.content.res.Configuration;
import android.content.SharedPreferences;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.support.design.circularreveal.cardview.CircularRevealCardView;
import android.support.design.widget.FloatingActionButton;
import android.view.Display;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.PopupMenu;
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.HashSet;
import java.util.List;
import java.util.Set;
public class LauncherActivity extends Activity implements DisplayManager.DisplayListener,
LoaderManager.LoaderCallbacks<List<AppEntry>>{
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
/**
* Main launcher activity. It's launch mode is configured as "singleTop" to allow showing on
* multiple displays and to ensure a single instance per each display.
*/
public class LauncherActivity extends FragmentActivity implements AppPickedCallback,
PopupMenu.OnMenuItemClickListener {
private Spinner mDisplaySpinner;
private List<Display> mDisplayList;
private int mSelectedDisplayId;
private View mScrimView;
private AppListAdapter mAppListAdapter;
private AppListAdapter mPinnedAppListAdapter;
private CircularRevealCardView mAppDrawerView;
private FloatingActionButton mFab;
private CheckBox mNewInstanceCheckBox;
private boolean mAppDrawerShown;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mScrimView = findViewById(R.id.Scrim);
mAppDrawerView = findViewById(R.id.FloatingSheet);
mFab = findViewById(R.id.FloatingActionButton);
mFab.setOnClickListener((View v) -> {
showAppDrawer(true);
});
mScrimView.setOnClickListener((View v) -> {
showAppDrawer(false);
});
mDisplaySpinner = findViewById(R.id.spinner);
mDisplaySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
@@ -69,37 +110,93 @@ public class LauncherActivity extends Activity implements DisplayManager.Display
}
});
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);
final ViewModelProvider viewModelProvider = new ViewModelProvider(getViewModelStore(),
new AndroidViewModelFactory((Application) getApplicationContext()));
getLoaderManager().initLoader(0, null, this);
mPinnedAppListAdapter = new AppListAdapter(this);
final GridView pinnedAppGridView = findViewById(R.id.pinned_app_grid);
pinnedAppGridView.setAdapter(mPinnedAppListAdapter);
pinnedAppGridView.setOnItemClickListener((adapterView, view, position, id) -> {
final AppEntry entry = mPinnedAppListAdapter.getItem(position);
launch(entry.getLaunchIntent());
});
final PinnedAppListViewModel pinnedAppListViewModel =
viewModelProvider.get(PinnedAppListViewModel.class);
pinnedAppListViewModel.getPinnedAppList().observe(this, data -> {
mPinnedAppListAdapter.setData(data);
});
mAppListAdapter = new AppListAdapter(this);
final GridView appGridView = findViewById(R.id.app_grid);
appGridView.setAdapter(mAppListAdapter);
appGridView.setOnItemClickListener((adapterView, view, position, id) -> {
final AppEntry entry = mAppListAdapter.getItem(position);
launch(entry.getLaunchIntent());
});
final AppListViewModel appListViewModel = viewModelProvider.get(AppListViewModel.class);
appListViewModel.getAppList().observe(this, data -> {
mAppListAdapter.setData(data);
});
findViewById(R.id.RefreshButton).setOnClickListener(this::refreshDisplayPicker);
mNewInstanceCheckBox = findViewById(R.id.NewInstanceCheckBox);
ImageButton optionsButton = findViewById(R.id.OptionsButton);
optionsButton.setOnClickListener((View v) -> {
PopupMenu popup = new PopupMenu(this,v);
popup.setOnMenuItemClickListener(this);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.context_menu, popup.getMenu());
popup.show();
});
}
@Override
protected void onResume() {
super.onResume();
updateDisplayList(null);
public boolean onMenuItemClick(MenuItem item) {
// Respond to picking one of the popup menu items.
switch (item.getItemId()) {
case R.id.add_app_shortcut:
FragmentManager fm = getSupportFragmentManager();
PinnedAppPickerDialog pickerDialogFragment =
PinnedAppPickerDialog.newInstance(mAppListAdapter, this);
pickerDialogFragment.show(fm, "fragment_app_picker");
return true;
case R.id.set_wallpaper:
Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
startActivity(Intent.createChooser(intent, getString(R.string.set_wallpaper)));
return true;
default:
return true;
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
showAppDrawer(false);
}
public void onBackPressed() {
// If the app drawer was shown - hide it. Otherwise, not doing anything since we don't want
// to close the launcher.
showAppDrawer(false);
}
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// A new intent will bring the launcher to top. Hide the app drawer to reset the state.
showAppDrawer(false);
}
void launch(Intent launchIntent) {
if (mSelectedDisplayId == -1) {
Toast.makeText(this, R.string.select_display, LENGTH_LONG).show();
return;
if (mNewInstanceCheckBox.isChecked()) {
launchIntent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
}
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mSelectedDisplayId);
if (mSelectedDisplayId != Display.INVALID_DISPLAY) {
options.setLaunchDisplayId(mSelectedDisplayId);
}
try {
startActivity(launchIntent, options.toBundle());
} catch (Exception e) {
@@ -112,34 +209,37 @@ public class LauncherActivity extends Activity implements DisplayManager.Display
}
}
/**
* 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) {
private void refreshDisplayPicker(View view) {
final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
final int currentDisplayId = wm.getDefaultDisplay().getDisplayId();
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) {
final boolean isCurrentDisplay = id == currentDisplayId;
if (isCurrentDisplay) {
preferredDisplayPosition = i;
}
final StringBuilder sb = new StringBuilder();
if (isCurrentDisplay) {
sb.append("[Current display] ");
}
sb.append(id).append(": ").append(display.getName());
if (isDisplayPrivate) {
sb.append(" (private)");
}
spinnerItems.add(sb.toString());
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,
@@ -149,33 +249,62 @@ public class LauncherActivity extends Activity implements DisplayManager.Display
mDisplaySpinner.setSelection(preferredDisplayPosition);
}
/**
* Store the picked app to persistent pinned list and update the loader.
*/
@Override
public void onDisplayAdded(int displayId) {
updateDisplayList(null);
public void onAppPicked(AppEntry appEntry) {
final SharedPreferences sp = getSharedPreferences(PINNED_APPS_KEY, 0);
Set<String> pinnedApps = sp.getStringSet(PINNED_APPS_KEY, null);
if (pinnedApps == null) {
pinnedApps = new HashSet<String>();
} else {
// Always need to create a new object to make sure that the changes are persisted.
pinnedApps = new HashSet<String>(pinnedApps);
}
pinnedApps.add(appEntry.getComponentName().flattenToString());
final SharedPreferences.Editor editor = sp.edit();
editor.putStringSet(PINNED_APPS_KEY, pinnedApps);
editor.apply();
}
@Override
public void onDisplayRemoved(int displayId) {
updateDisplayList(null);
/**
* Show/hide app drawer card with animation.
*/
private void showAppDrawer(boolean show) {
if (show == mAppDrawerShown) {
return;
}
final Animator animator = revealAnimator(mAppDrawerView, show);
if (show) {
mAppDrawerShown = true;
mAppDrawerView.setVisibility(View.VISIBLE);
mScrimView.setVisibility(View.VISIBLE);
mFab.setVisibility(View.INVISIBLE);
refreshDisplayPicker(null);
} else {
mAppDrawerShown = false;
mScrimView.setVisibility(View.INVISIBLE);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mAppDrawerView.setVisibility(View.INVISIBLE);
mFab.setVisibility(View.VISIBLE);
}
});
}
animator.start();
}
@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);
/**
* Create reveal/hide animator for app list card.
*/
private Animator revealAnimator(View view, boolean open) {
final int radius = (int) Math.hypot((double) view.getWidth(), (double) view.getHeight());
return ViewAnimationUtils.createCircularReveal(view, view.getRight(), view.getBottom(),
open ? 0 : radius, open ? radius : 0);
}
}

View File

@@ -1,48 +0,0 @@
/**
* 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();
}
}

View File

@@ -0,0 +1,120 @@
/**
* 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 com.example.android.multidisplay.launcher.PinnedAppListViewModel.PINNED_APPS_KEY;
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* A view model that provides a list of activities that were pinned by user to always display on
* home screen.
* The pinned activities are stored in {@link SharedPreferences} to keep the sample simple :).
*/
public class PinnedAppListViewModel extends AndroidViewModel {
final static String PINNED_APPS_KEY = "pinned_apps";
private final PinnedAppListLiveData mLiveData;
public PinnedAppListViewModel(Application application) {
super(application);
mLiveData = new PinnedAppListLiveData(application);
}
public LiveData<List<AppEntry>> getPinnedAppList() {
return mLiveData;
}
}
class PinnedAppListLiveData extends LiveData<List<AppEntry>> {
private final Context mContext;
private final PackageManager mPackageManager;
// Store listener reference, so it won't be GC-ed.
private final SharedPreferences.OnSharedPreferenceChangeListener mChangeListener;
private int mCurrentDataVersion;
public PinnedAppListLiveData(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
final SharedPreferences prefs = context.getSharedPreferences(PINNED_APPS_KEY, 0);
mChangeListener = (preferences, key) -> {
loadData();
};
prefs.registerOnSharedPreferenceChangeListener(mChangeListener);
loadData();
}
private void loadData() {
final int loadDataVersion = ++mCurrentDataVersion;
new AsyncTask<Void, Void, List<AppEntry>>() {
@Override
protected List<AppEntry> doInBackground(Void... voids) {
List<AppEntry> entries = new ArrayList<>();
final SharedPreferences sp = mContext.getSharedPreferences(PINNED_APPS_KEY, 0);
final Set<String> pinnedAppsComponents = sp.getStringSet(PINNED_APPS_KEY, null);
if (pinnedAppsComponents == null) {
return null;
}
for (String componentString : pinnedAppsComponents) {
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.setComponent(ComponentName.unflattenFromString(componentString));
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
final List<ResolveInfo> apps = mPackageManager.queryIntentActivities(mainIntent,
PackageManager.GET_META_DATA);
if (apps != null) {
for (ResolveInfo app : apps) {
final AppEntry entry = new AppEntry(app, mPackageManager);
entries.add(entry);
}
}
}
return entries;
}
@Override
protected void onPostExecute(List<AppEntry> data) {
if (mCurrentDataVersion == loadDataVersion) {
setValue(data);
}
}
}.execute();
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import com.example.android.multidisplay.R;
import androidx.fragment.app.DialogFragment;
/**
* Callback to be invoked when an app was picked.
*/
interface AppPickedCallback {
void onAppPicked(AppEntry appEntry);
}
/**
* Dialog that provides the user with a list of available apps to pin to the home screen.
*/
public class PinnedAppPickerDialog extends DialogFragment {
private AppListAdapter mAppListAdapter;
private AppPickedCallback mAppPickerCallback;
public PinnedAppPickerDialog() {
}
public static PinnedAppPickerDialog newInstance(AppListAdapter appListAdapter,
AppPickedCallback callback) {
PinnedAppPickerDialog frag = new PinnedAppPickerDialog();
frag.mAppListAdapter = appListAdapter;
frag.mAppPickerCallback = callback;
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.app_picker_dialog, container);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
GridView appGridView = view.findViewById(R.id.picker_app_grid);
appGridView.setAdapter(mAppListAdapter);
appGridView.setOnItemClickListener((adapterView, itemView, position, id) -> {
final AppEntry entry = mAppListAdapter.getItem(position);
mAppPickerCallback.onAppPicked(entry);
dismiss();
});
}
}