summaryrefslogtreecommitdiffstats
path: root/SecondaryDisplayLauncher/src
diff options
context:
space:
mode:
authorChilun <chilunhuang@google.com>2018-11-29 20:38:22 +0800
committerChilun Huang <chilunhuang@google.com>2018-12-13 09:21:47 +0000
commit3516bd89d9665787fdb3db73e4922361bb307380 (patch)
tree70ab33b412ec5fa1c67cd955b31836b0720138a5 /SecondaryDisplayLauncher/src
parent4d64da3259d83fa85b6db16a926276a5e283ac3f (diff)
downloadpackages_apps_Trebuchet-3516bd89d9665787fdb3db73e4922361bb307380.tar.gz
packages_apps_Trebuchet-3516bd89d9665787fdb3db73e4922361bb307380.tar.bz2
packages_apps_Trebuchet-3516bd89d9665787fdb3db73e4922361bb307380.zip
Add a secondary launcher activity (1/2)
This secondary launcher activity with new category SECONDARY_HOME and multiple instance supporrted could be used on sencondary display. Bug: 118206886 Bug: 114329798 Test: Manual Change-Id: Ibaecf8ef7614389760d0fcc547ef6d378a921583
Diffstat (limited to 'SecondaryDisplayLauncher/src')
-rw-r--r--SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java58
-rw-r--r--SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java63
-rw-r--r--SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java126
-rw-r--r--SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java120
-rw-r--r--SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java74
-rw-r--r--SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java217
6 files changed, 658 insertions, 0 deletions
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java b/SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java
new file mode 100644
index 000000000..3017b81c1
--- /dev/null
+++ b/SecondaryDisplayLauncher/src/com/android/launcher3/AppEntry.java
@@ -0,0 +1,58 @@
+/**
+ * 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.android.launcher3;
+
+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 {
+
+ private String mLabel;
+ private Drawable mIcon;
+ private Intent mLaunchIntent;
+
+ 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; }
+
+ ComponentName getComponentName() {
+ return mLaunchIntent.getComponent();
+ }
+
+ @Override
+ public String toString() {
+ return mLabel;
+ }
+}
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java
new file mode 100644
index 000000000..aa115cb57
--- /dev/null
+++ b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListAdapter.java
@@ -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.android.launcher3;
+
+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.android.launcher3.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;
+ }
+}
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java
new file mode 100644
index 000000000..914fd5e01
--- /dev/null
+++ b/SecondaryDisplayLauncher/src/com/android/launcher3/AppListViewModel.java
@@ -0,0 +1,126 @@
+/**
+ * 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.android.launcher3;
+
+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();
+ }
+} \ No newline at end of file
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java
new file mode 100644
index 000000000..4f92038ae
--- /dev/null
+++ b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppListViewModel.java
@@ -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.android.launcher3;
+
+import static com.android.launcher3.PinnedAppListViewModel.PINNED_APPS_KEY;
+
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+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;
+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();
+ }
+} \ No newline at end of file
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java
new file mode 100644
index 000000000..02e6e4a9c
--- /dev/null
+++ b/SecondaryDisplayLauncher/src/com/android/launcher3/PinnedAppPickerDialog.java
@@ -0,0 +1,74 @@
+/**
+ * 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.android.launcher3;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridView;
+
+import androidx.fragment.app.DialogFragment;
+
+import com.android.launcher3.R;
+
+/**
+ * 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();
+ });
+ }
+} \ No newline at end of file
diff --git a/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
new file mode 100644
index 000000000..5e8b40246
--- /dev/null
+++ b/SecondaryDisplayLauncher/src/com/android/launcher3/SecondaryDisplayLauncher.java
@@ -0,0 +1,217 @@
+/**
+ * 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.android.launcher3;
+
+import static com.android.launcher3.PinnedAppListViewModel.PINNED_APPS_KEY;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.AlertDialog;
+import android.app.Application;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.design.circularreveal.cardview.CircularRevealCardView;
+import android.support.design.widget.FloatingActionButton;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.widget.GridView;
+import android.widget.ImageButton;
+import android.widget.PopupMenu;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Secondary 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 SecondaryDisplayLauncher extends FragmentActivity implements AppPickedCallback,
+ PopupMenu.OnMenuItemClickListener {
+
+ private AppListAdapter mAppListAdapter;
+ private AppListAdapter mPinnedAppListAdapter;
+ private CircularRevealCardView mAppDrawerView;
+ private FloatingActionButton mFab;
+
+ private boolean mAppDrawerShown;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.secondary_display_launcher);
+
+ mAppDrawerView = findViewById(R.id.FloatingSheet);
+ mFab = findViewById(R.id.FloatingActionButton);
+
+ mFab.setOnClickListener((View v) -> {
+ showAppDrawer(true);
+ });
+
+ final ViewModelProvider viewModelProvider = new ViewModelProvider(getViewModelStore(),
+ new AndroidViewModelFactory((Application) getApplicationContext()));
+
+ 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);
+ });
+
+ 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
+ public boolean onMenuItemClick(MenuItem item) {
+ // Respond to picking one of the popup menu items.
+ final int id = item.getItemId();
+ if (id == R.id.add_app_shortcut) {
+ FragmentManager fm = getSupportFragmentManager();
+ PinnedAppPickerDialog pickerDialogFragment =
+ PinnedAppPickerDialog.newInstance(mAppListAdapter, this);
+ pickerDialogFragment.show(fm, "fragment_app_picker");
+ return true;
+ } else if (id == R.id.set_wallpaper) {
+ Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+ startActivity(Intent.createChooser(intent, getString(R.string.set_wallpaper)));
+ return true;
+ }
+
+ 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) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ startActivity(launchIntent);
+ } 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();
+ }
+ }
+
+ /**
+ * Store the picked app to persistent pinned list and update the loader.
+ */
+ @Override
+ 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();
+ }
+
+ /**
+ * 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);
+ mFab.setVisibility(View.INVISIBLE);
+ } else {
+ mAppDrawerShown = false;
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mAppDrawerView.setVisibility(View.INVISIBLE);
+ mFab.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+ animator.start();
+ }
+
+ /**
+ * 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);
+ }
+}