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:
@@ -22,6 +22,11 @@ LOCAL_MODULE_TAGS := samples
|
|||||||
# Only compile source java files in this apk.
|
# Only compile source java files in this apk.
|
||||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||||
|
|
||||||
|
LOCAL_STATIC_ANDROID_LIBRARIES += \
|
||||||
|
androidx.design_design \
|
||||||
|
androidx.lifecycle_lifecycle-livedata \
|
||||||
|
androidx.lifecycle_lifecycle-viewmodel
|
||||||
|
|
||||||
LOCAL_PACKAGE_NAME := MultiDisplay
|
LOCAL_PACKAGE_NAME := MultiDisplay
|
||||||
|
|
||||||
LOCAL_SDK_VERSION := current
|
LOCAL_SDK_VERSION := current
|
||||||
|
|||||||
@@ -22,7 +22,10 @@
|
|||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
<activity
|
<activity
|
||||||
android:name=".launcher.LauncherActivity"
|
android:name=".launcher.LauncherActivity"
|
||||||
android:label="@string/md_launcher">
|
android:label="@string/md_launcher"
|
||||||
|
android:theme="@style/LauncherTheme"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.HOME" />
|
<category android:name="android.intent.category.HOME" />
|
||||||
|
|||||||
22
samples/MultiDisplay/res/drawable/ic_apps.xml
Normal file
22
samples/MultiDisplay/res/drawable/ic_apps.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
|
||||||
|
</vector>
|
||||||
22
samples/MultiDisplay/res/drawable/ic_refresh.xml
Normal file
22
samples/MultiDisplay/res/drawable/ic_refresh.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||||
|
</vector>
|
||||||
22
samples/MultiDisplay/res/drawable/ic_settings.xml
Normal file
22
samples/MultiDisplay/res/drawable/ic_settings.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
|
||||||
|
</vector>
|
||||||
63
samples/MultiDisplay/res/layout-sw600dp/activity_main.xml
Normal file
63
samples/MultiDisplay/res/layout-sw600dp/activity_main.xml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/RootView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/launcher_bg_color"
|
||||||
|
android:fitsSystemWindows="true" >
|
||||||
|
|
||||||
|
<GridView
|
||||||
|
android:id="@+id/pinned_app_grid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="@dimen/app_grid_margin_top"
|
||||||
|
android:layout_marginStart="@dimen/app_grid_margin_left"
|
||||||
|
android:layout_marginEnd="@dimen/app_grid_margin_right"
|
||||||
|
android:columnWidth="@dimen/app_list_col_width"
|
||||||
|
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
|
||||||
|
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
|
||||||
|
android:numColumns="auto_fit" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/OptionsButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|start"
|
||||||
|
android:layout_marginStart="@dimen/options_button_margin"
|
||||||
|
android:layout_marginBottom="@dimen/options_button_margin"
|
||||||
|
android:src="@drawable/ic_settings"
|
||||||
|
android:background="@null"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/Scrim"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/mtrl_background_scrim"
|
||||||
|
android:visibility="invisible"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="@dimen/app_picker_width"
|
||||||
|
android:layout_height="@dimen/app_picker_height"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="@dimen/app_picker_fab_margin">
|
||||||
|
|
||||||
|
<include layout="@layout/app_picker_layout"/>
|
||||||
|
</FrameLayout>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
64
samples/MultiDisplay/res/layout-sw720dp/activity_main.xml
Normal file
64
samples/MultiDisplay/res/layout-sw720dp/activity_main.xml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/RootView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/launcher_bg_color"
|
||||||
|
android:fitsSystemWindows="true" >
|
||||||
|
|
||||||
|
<GridView
|
||||||
|
android:id="@+id/pinned_app_grid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="@dimen/app_grid_margin_top"
|
||||||
|
android:layout_marginStart="@dimen/app_grid_margin_left"
|
||||||
|
android:layout_marginEnd="@dimen/app_grid_margin_right"
|
||||||
|
android:columnWidth="@dimen/app_list_col_width"
|
||||||
|
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
|
||||||
|
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
|
||||||
|
android:numColumns="auto_fit" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/OptionsButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|start"
|
||||||
|
android:layout_marginStart="@dimen/options_button_margin"
|
||||||
|
android:layout_marginBottom="@dimen/options_button_margin"
|
||||||
|
android:src="@drawable/ic_settings"
|
||||||
|
android:background="@null"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/Scrim"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/mtrl_background_scrim"
|
||||||
|
android:visibility="invisible"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="@dimen/app_picker_width"
|
||||||
|
android:layout_height="@dimen/app_picker_height"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginEnd="@dimen/app_picker_fab_margin"
|
||||||
|
android:layout_marginBottom="@dimen/app_picker_fab_margin">
|
||||||
|
|
||||||
|
<include layout="@layout/app_picker_layout"/>
|
||||||
|
</FrameLayout>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -15,33 +15,16 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/RootView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:background="@color/launcher_bg_color"
|
||||||
|
android:fitsSystemWindows="true" >
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinner"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/button2"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/refresh"
|
|
||||||
android:onClick="updateDisplayList"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<GridView
|
<GridView
|
||||||
android:id="@+id/app_grid"
|
android:id="@+id/pinned_app_grid"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="@dimen/app_grid_margin_top"
|
android:layout_marginTop="@dimen/app_grid_margin_top"
|
||||||
@@ -50,6 +33,32 @@
|
|||||||
android:columnWidth="@dimen/app_list_col_width"
|
android:columnWidth="@dimen/app_list_col_width"
|
||||||
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
|
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
|
||||||
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
|
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
|
||||||
android:numColumns="auto_fit">
|
android:numColumns="auto_fit" />
|
||||||
</GridView>
|
|
||||||
</LinearLayout>
|
<ImageButton
|
||||||
|
android:id="@+id/OptionsButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|start"
|
||||||
|
android:layout_marginStart="@dimen/options_button_margin"
|
||||||
|
android:layout_marginBottom="@dimen/options_button_margin"
|
||||||
|
android:src="@drawable/ic_settings"
|
||||||
|
android:background="@null"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/Scrim"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/mtrl_background_scrim"
|
||||||
|
android:visibility="invisible" />
|
||||||
|
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="@dimen/app_picker_fab_margin">
|
||||||
|
|
||||||
|
<include layout="@layout/app_picker_layout"/>
|
||||||
|
</FrameLayout>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@@ -32,4 +32,4 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="1" />
|
android:maxLines="1" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
34
samples/MultiDisplay/res/layout/app_picker_dialog.xml
Normal file
34
samples/MultiDisplay/res/layout/app_picker_dialog.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<GridView
|
||||||
|
android:id="@+id/picker_app_grid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="@dimen/app_grid_margin_top"
|
||||||
|
android:layout_marginStart="@dimen/app_grid_margin_left"
|
||||||
|
android:layout_marginEnd="@dimen/app_grid_margin_right"
|
||||||
|
android:columnWidth="@dimen/app_list_col_width"
|
||||||
|
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
|
||||||
|
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
|
||||||
|
android:numColumns="auto_fit" />
|
||||||
|
</FrameLayout>
|
||||||
81
samples/MultiDisplay/res/layout/app_picker_layout.xml
Normal file
81
samples/MultiDisplay/res/layout/app_picker_layout.xml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<android.support.design.circularreveal.cardview.CircularRevealCardView
|
||||||
|
android:id="@+id/FloatingSheet"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="invisible">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="@dimen/app_grid_margin_left"
|
||||||
|
android:layout_marginEnd="@dimen/app_grid_margin_right"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/app_grid_margin_top"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinner"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/RefreshButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_refresh"
|
||||||
|
android:background="@null"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/NewInstanceCheckBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/app_grid_margin_top"
|
||||||
|
android:text="@string/new_instance" />
|
||||||
|
|
||||||
|
<GridView
|
||||||
|
android:id="@+id/app_grid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="@dimen/app_grid_margin_top"
|
||||||
|
android:columnWidth="@dimen/app_list_col_width"
|
||||||
|
android:verticalSpacing="@dimen/app_list_horizontal_spacing"
|
||||||
|
android:horizontalSpacing="@dimen/app_list_vertical_spacing"
|
||||||
|
android:numColumns="auto_fit" />
|
||||||
|
</LinearLayout>
|
||||||
|
</android.support.design.circularreveal.cardview.CircularRevealCardView>
|
||||||
|
|
||||||
|
<android.support.design.widget.FloatingActionButton
|
||||||
|
android:id="@+id/FloatingActionButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:src="@drawable/ic_apps"/>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
23
samples/MultiDisplay/res/menu/context_menu.xml
Normal file
23
samples/MultiDisplay/res/menu/context_menu.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@+id/add_app_shortcut"
|
||||||
|
android:title="@string/add_app_shortcut" />
|
||||||
|
<item android:id="@+id/set_wallpaper"
|
||||||
|
android:title="@string/set_wallpaper" />
|
||||||
|
</menu>
|
||||||
22
samples/MultiDisplay/res/values-sw600dp/dimens.xml
Normal file
22
samples/MultiDisplay/res/values-sw600dp/dimens.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<dimen name="app_picker_width">400dp</dimen>
|
||||||
|
<dimen name="app_picker_height">400dp</dimen>
|
||||||
|
<dimen name="app_picker_fab_margin">60dp</dimen>
|
||||||
|
</resources>
|
||||||
22
samples/MultiDisplay/res/values-sw720dp/dimens.xml
Normal file
22
samples/MultiDisplay/res/values-sw720dp/dimens.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<dimen name="app_picker_width">660dp</dimen>
|
||||||
|
<dimen name="app_picker_height">660dp</dimen>
|
||||||
|
<dimen name="app_picker_fab_margin">70dp</dimen>
|
||||||
|
</resources>
|
||||||
21
samples/MultiDisplay/res/values/colors.xml
Normal file
21
samples/MultiDisplay/res/values/colors.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<color name="mtrl_background_scrim">#76000000</color>
|
||||||
|
<color name="launcher_bg_color">#884e8391</color>
|
||||||
|
</resources>
|
||||||
@@ -24,8 +24,12 @@
|
|||||||
<dimen name="app_list_vertical_spacing">24dp</dimen>
|
<dimen name="app_list_vertical_spacing">24dp</dimen>
|
||||||
<dimen name="app_icon_width">64dp</dimen>
|
<dimen name="app_icon_width">64dp</dimen>
|
||||||
<dimen name="app_icon_height">64dp</dimen>
|
<dimen name="app_icon_height">64dp</dimen>
|
||||||
<dimen name="app_grid_margin_bottom">48dp</dimen>
|
|
||||||
<dimen name="app_grid_margin_top">24dp</dimen>
|
<dimen name="app_grid_margin_top">24dp</dimen>
|
||||||
<dimen name="app_grid_margin_left">8dp</dimen>
|
<dimen name="app_grid_margin_left">8dp</dimen>
|
||||||
<dimen name="app_grid_margin_right">8dp</dimen>
|
<dimen name="app_grid_margin_right">8dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="app_picker_width">300dp</dimen>
|
||||||
|
<dimen name="app_picker_height">300dp</dimen>
|
||||||
|
<dimen name="app_picker_fab_margin">20dp</dimen>
|
||||||
|
<dimen name="options_button_margin">20dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">MultiDisplay</string>
|
<string name="app_name">MultiDisplay</string>
|
||||||
<string name="md_launcher">MD Launcher</string>
|
<string name="md_launcher">MD Launcher</string>
|
||||||
<string name="refresh">Refresh</string>
|
|
||||||
<string name="couldnt_launch">Couldn\'t launch the activity</string>
|
<string name="couldnt_launch">Couldn\'t launch the activity</string>
|
||||||
<string name="select_display">Select a display</string>
|
<string name="select_display">Select a display</string>
|
||||||
|
<string name="add_app_shortcut">Add app shortcut</string>
|
||||||
|
<string name="set_wallpaper">Set wallpaper</string>
|
||||||
|
<string name="new_instance">Request launch in new instance</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
28
samples/MultiDisplay/res/values/styles.xml
Normal file
28
samples/MultiDisplay/res/values/styles.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<style name="LauncherTheme" parent="Theme.AppCompat.Light.NoActionBar" >
|
||||||
|
<item name="android:windowShowWallpaper">true</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
|
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -25,6 +25,10 @@ import android.graphics.drawable.Drawable;
|
|||||||
/** An entry that represents a single activity that can be launched. */
|
/** An entry that represents a single activity that can be launched. */
|
||||||
public class AppEntry {
|
public class AppEntry {
|
||||||
|
|
||||||
|
private String mLabel;
|
||||||
|
private Drawable mIcon;
|
||||||
|
private Intent mLaunchIntent;
|
||||||
|
|
||||||
AppEntry(ResolveInfo info, PackageManager packageManager) {
|
AppEntry(ResolveInfo info, PackageManager packageManager) {
|
||||||
mLabel = info.loadLabel(packageManager).toString();
|
mLabel = info.loadLabel(packageManager).toString();
|
||||||
mIcon = info.loadIcon(packageManager);
|
mIcon = info.loadIcon(packageManager);
|
||||||
@@ -43,12 +47,12 @@ public class AppEntry {
|
|||||||
|
|
||||||
Intent getLaunchIntent() { return mLaunchIntent; }
|
Intent getLaunchIntent() { return mLaunchIntent; }
|
||||||
|
|
||||||
|
ComponentName getComponentName() {
|
||||||
|
return mLaunchIntent.getComponent();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return mLabel;
|
return mLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String mLabel;
|
|
||||||
private Drawable mIcon;
|
|
||||||
private Intent mLaunchIntent;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,46 +16,87 @@
|
|||||||
|
|
||||||
package com.example.android.multidisplay.launcher;
|
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.ActivityOptions;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.LoaderManager;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.Loader;
|
import android.content.res.Configuration;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.design.circularreveal.cardview.CircularRevealCardView;
|
||||||
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewAnimationUtils;
|
||||||
|
import android.view.WindowManager;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
|
||||||
import android.widget.AdapterView.OnItemSelectedListener;
|
import android.widget.AdapterView.OnItemSelectedListener;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.CheckBox;
|
||||||
import android.widget.GridView;
|
import android.widget.GridView;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.example.android.multidisplay.R;
|
import com.example.android.multidisplay.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class LauncherActivity extends Activity implements DisplayManager.DisplayListener,
|
import androidx.fragment.app.FragmentActivity;
|
||||||
LoaderManager.LoaderCallbacks<List<AppEntry>>{
|
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 Spinner mDisplaySpinner;
|
||||||
private List<Display> mDisplayList;
|
private List<Display> mDisplayList;
|
||||||
private int mSelectedDisplayId;
|
private int mSelectedDisplayId;
|
||||||
|
private View mScrimView;
|
||||||
private AppListAdapter mAppListAdapter;
|
private AppListAdapter mAppListAdapter;
|
||||||
|
private AppListAdapter mPinnedAppListAdapter;
|
||||||
|
private CircularRevealCardView mAppDrawerView;
|
||||||
|
private FloatingActionButton mFab;
|
||||||
|
private CheckBox mNewInstanceCheckBox;
|
||||||
|
|
||||||
|
private boolean mAppDrawerShown;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
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 = findViewById(R.id.spinner);
|
||||||
mDisplaySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
|
mDisplaySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -69,37 +110,93 @@ public class LauncherActivity extends Activity implements DisplayManager.Display
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final GridView appGridView = findViewById(R.id.app_grid);
|
final ViewModelProvider viewModelProvider = new ViewModelProvider(getViewModelStore(),
|
||||||
mAppListAdapter = new AppListAdapter(this);
|
new AndroidViewModelFactory((Application) getApplicationContext()));
|
||||||
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);
|
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
|
@Override
|
||||||
protected void onResume() {
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
super.onResume();
|
// Respond to picking one of the popup menu items.
|
||||||
updateDisplayList(null);
|
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) {
|
void launch(Intent launchIntent) {
|
||||||
if (mSelectedDisplayId == -1) {
|
if (mNewInstanceCheckBox.isChecked()) {
|
||||||
Toast.makeText(this, R.string.select_display, LENGTH_LONG).show();
|
launchIntent.addFlags(
|
||||||
return;
|
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();
|
final ActivityOptions options = ActivityOptions.makeBasic();
|
||||||
options.setLaunchDisplayId(mSelectedDisplayId);
|
if (mSelectedDisplayId != Display.INVALID_DISPLAY) {
|
||||||
|
options.setLaunchDisplayId(mSelectedDisplayId);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
startActivity(launchIntent, options.toBundle());
|
startActivity(launchIntent, options.toBundle());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -112,34 +209,37 @@ public class LauncherActivity extends Activity implements DisplayManager.Display
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void refreshDisplayPicker(View view) {
|
||||||
* Read the list of currently connected displays and pick one.
|
final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
|
||||||
* When the list changes it'll try to keep the previously selected display. If that one won't be
|
final int currentDisplayId = wm.getDefaultDisplay().getDisplayId();
|
||||||
* available, it'll pick the display with biggest id (last connected).
|
|
||||||
*/
|
|
||||||
public void updateDisplayList(View view) {
|
|
||||||
final DisplayManager dm = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
|
final DisplayManager dm = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
|
||||||
mDisplayList = Arrays.asList(dm.getDisplays());
|
mDisplayList = Arrays.asList(dm.getDisplays());
|
||||||
final List<String> spinnerItems = new ArrayList<>();
|
final List<String> spinnerItems = new ArrayList<>();
|
||||||
|
|
||||||
int preferredDisplayPosition = -1;
|
int preferredDisplayPosition = -1;
|
||||||
int biggestId = -1, biggestPos = -1;
|
int biggestId = -1, biggestPos = -1;
|
||||||
for (int i = 0; i < mDisplayList.size(); i++) {
|
for (int i = 0; i < mDisplayList.size(); i++) {
|
||||||
final Display display = mDisplayList.get(i);
|
final Display display = mDisplayList.get(i);
|
||||||
final int id = display.getDisplayId();
|
final int id = display.getDisplayId();
|
||||||
final boolean isDisplayPrivate = (display.getFlags() & Display.FLAG_PRIVATE) != 0;
|
final boolean isDisplayPrivate = (display.getFlags() & Display.FLAG_PRIVATE) != 0;
|
||||||
spinnerItems.add("" + id + ": " + display.getName()
|
final boolean isCurrentDisplay = id == currentDisplayId;
|
||||||
+ (isDisplayPrivate ? " (private)" : ""));
|
if (isCurrentDisplay) {
|
||||||
if (id == mSelectedDisplayId) {
|
|
||||||
preferredDisplayPosition = i;
|
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) {
|
if (display.getDisplayId() > biggestId) {
|
||||||
biggestId = display.getDisplayId();
|
biggestId = display.getDisplayId();
|
||||||
biggestPos = i;
|
biggestPos = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (preferredDisplayPosition == -1) {
|
|
||||||
preferredDisplayPosition = biggestPos;
|
|
||||||
}
|
|
||||||
mSelectedDisplayId = mDisplayList.get(preferredDisplayPosition).getDisplayId();
|
mSelectedDisplayId = mDisplayList.get(preferredDisplayPosition).getDisplayId();
|
||||||
|
|
||||||
final ArrayAdapter<String> displayAdapter = new ArrayAdapter<>(this,
|
final ArrayAdapter<String> displayAdapter = new ArrayAdapter<>(this,
|
||||||
@@ -149,33 +249,62 @@ public class LauncherActivity extends Activity implements DisplayManager.Display
|
|||||||
mDisplaySpinner.setSelection(preferredDisplayPosition);
|
mDisplaySpinner.setSelection(preferredDisplayPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the picked app to persistent pinned list and update the loader.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDisplayAdded(int displayId) {
|
public void onAppPicked(AppEntry appEntry) {
|
||||||
updateDisplayList(null);
|
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) {
|
* Show/hide app drawer card with animation.
|
||||||
updateDisplayList(null);
|
*/
|
||||||
|
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) {
|
* Create reveal/hide animator for app list card.
|
||||||
updateDisplayList(null);
|
*/
|
||||||
}
|
private Animator revealAnimator(View view, boolean open) {
|
||||||
|
final int radius = (int) Math.hypot((double) view.getWidth(), (double) view.getHeight());
|
||||||
@Override
|
return ViewAnimationUtils.createCircularReveal(view, view.getRight(), view.getBottom(),
|
||||||
public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
|
open ? 0 : radius, open ? radius : 0);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user