aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2018-08-07 16:51:25 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2018-08-07 16:51:25 +0000
commitc6c454c3b017244644e3ddba3c2975a386605359 (patch)
treea0d80646918ef7aa73fd720b9e34828c755d9a3a
parent8f2b472ae1b1914335e3834242c33c26d1d76cad (diff)
parentcc1b9f3e19e2decc5fd6be694698ed40e86a5e80 (diff)
downloadexperimental-oreo-mr1-1.2-iot-release.tar.gz
-rw-r--r--FillService/AndroidManifest.xml8
-rw-r--r--FillService/res/xml/autofill_service_config.xml20
-rw-r--r--FillService/src/foo/bar/fill/FillService.java120
-rw-r--r--KBars/Android.mk34
-rw-r--r--KBars/app/build.gradle34
-rw-r--r--KBars/app/src/main/AndroidManifest.xml32
-rw-r--r--KBars/app/src/main/assets/background.pngbin0 -> 20352 bytes
-rw-r--r--KBars/app/src/main/java/js/kbars/CameraBackgroundMenuItem.java102
-rw-r--r--KBars/app/src/main/java/js/kbars/DropShadowActivity.java40
-rw-r--r--KBars/app/src/main/java/js/kbars/FitSystemWindowsActivity.java184
-rw-r--r--KBars/app/src/main/java/js/kbars/IdentifyBarsButton.java21
-rw-r--r--KBars/app/src/main/java/js/kbars/ImageBackgroundMenuItem.java109
-rw-r--r--KBars/app/src/main/java/js/kbars/ImmersiveModeToggleButton.java79
-rw-r--r--KBars/app/src/main/java/js/kbars/KBarsActivity.java273
-rw-r--r--KBars/app/src/main/java/js/kbars/KBarsCar.java38
-rw-r--r--KBars/app/src/main/java/js/kbars/KBarsDream.java47
-rw-r--r--KBars/app/src/main/java/js/kbars/LightsOutModeToggleButton.java26
-rw-r--r--KBars/app/src/main/java/js/kbars/MediaModeToggleButton.java95
-rw-r--r--KBars/app/src/main/java/js/kbars/RandomColorBackgroundMenuItem.java29
-rw-r--r--KBars/app/src/main/java/js/kbars/SystemGestureDebugger.java216
-rw-r--r--KBars/app/src/main/java/js/kbars/ToastActivity.java117
-rw-r--r--KBars/app/src/main/java/js/kbars/TouchTrackingLayout.java298
-rw-r--r--KBars/app/src/main/java/js/kbars/TransparencyToggleButton.java43
-rw-r--r--KBars/app/src/main/java/js/kbars/Util.java42
-rw-r--r--KBars/app/src/main/res/drawable-hdpi-v4/ic_launcher.pngbin0 -> 2327 bytes
-rw-r--r--KBars/app/src/main/res/drawable-mdpi-v4/ic_launcher.pngbin0 -> 1530 bytes
-rw-r--r--KBars/app/src/main/res/drawable-xhdpi-v4/ic_launcher.pngbin0 -> 3235 bytes
-rw-r--r--KBars/app/src/main/res/drawable-xxhdpi-v4/ic_launcher.pngbin0 -> 4663 bytes
-rw-r--r--KBars/app/src/main/res/raw/clipcanvas.mp4bin0 -> 1137202 bytes
-rw-r--r--KBars/app/src/main/res/values/strings.xml8
-rw-r--r--KBars/app/src/main/res/values/styles.xml7
-rw-r--r--KBars/build.gradle28
-rw-r--r--KBars/settings.gradle1
-rw-r--r--PermissionApp/Android.mk3
-rw-r--r--PermissionApp/AndroidManifest.xml1
-rw-r--r--PermissionApp/src/foo/bar/permission/PermissionActivity.java4
-rw-r--r--PrintApp/Android.mk1
-rw-r--r--PrintService/Android.mk1
-rw-r--r--SlicesApp/Android.mk29
-rw-r--r--SlicesApp/AndroidManifest.xml63
-rw-r--r--SlicesApp/res/drawable/ic_add.xml27
-rw-r--r--SlicesApp/res/drawable/ic_camera.xml18
-rw-r--r--SlicesApp/res/drawable/ic_car.xml17
-rw-r--r--SlicesApp/res/drawable/ic_create.xml9
-rw-r--r--SlicesApp/res/drawable/ic_done.xml13
-rw-r--r--SlicesApp/res/drawable/ic_down.xml13
-rw-r--r--SlicesApp/res/drawable/ic_home.xml13
-rw-r--r--SlicesApp/res/drawable/ic_list.xml14
-rw-r--r--SlicesApp/res/drawable/ic_next.xml13
-rw-r--r--SlicesApp/res/drawable/ic_pause.xml13
-rw-r--r--SlicesApp/res/drawable/ic_photo.xml14
-rw-r--r--SlicesApp/res/drawable/ic_play.xml13
-rw-r--r--SlicesApp/res/drawable/ic_prev.xml13
-rw-r--r--SlicesApp/res/drawable/ic_remove.xml27
-rw-r--r--SlicesApp/res/drawable/ic_reply.xml13
-rw-r--r--SlicesApp/res/drawable/ic_share.xml18
-rw-r--r--SlicesApp/res/drawable/ic_slice.xml9
-rw-r--r--SlicesApp/res/drawable/ic_up.xml13
-rw-r--r--SlicesApp/res/drawable/ic_video.xml14
-rw-r--r--SlicesApp/res/drawable/ic_voice.xml16
-rw-r--r--SlicesApp/res/drawable/ic_work.xml14
-rw-r--r--SlicesApp/res/drawable/mady.jpgbin0 -> 67605 bytes
-rw-r--r--SlicesApp/res/drawable/weather_1.pngbin0 -> 7909 bytes
-rw-r--r--SlicesApp/res/drawable/weather_2.pngbin0 -> 10095 bytes
-rw-r--r--SlicesApp/res/drawable/weather_3.pngbin0 -> 8737 bytes
-rw-r--r--SlicesApp/res/drawable/weather_4.pngbin0 -> 6377 bytes
-rw-r--r--SlicesApp/res/layout/activity_layout.xml92
-rw-r--r--SlicesApp/res/values/strings.xml18
-rw-r--r--SlicesApp/src/com/android/experimental/slicesapp/SlicesActivity.java132
-rw-r--r--SlicesApp/src/com/android/experimental/slicesapp/SlicesBroadcastReceiver.java29
-rw-r--r--SlicesApp/src/com/android/experimental/slicesapp/SlicesProvider.java380
-rw-r--r--TestBack/src/foo/bar/testback/AccessibilityFocusManager.java64
-rw-r--r--TestBack/src/foo/bar/testback/AccessibilityNodeInfoUtils.java54
-rw-r--r--TestBack/src/foo/bar/testback/TestBackService.java120
74 files changed, 3273 insertions, 83 deletions
diff --git a/FillService/AndroidManifest.xml b/FillService/AndroidManifest.xml
index f9128e1..da039b6 100644
--- a/FillService/AndroidManifest.xml
+++ b/FillService/AndroidManifest.xml
@@ -4,10 +4,16 @@
<application>
<service android:name=".FillService"
- android:permission="android.permission.BIND_AUTOFILL">
+ android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
+
+ <meta-data
+ android:name="android.autofill"
+ android:resource="@xml/autofill_service_config">
+ </meta-data>
+
</service>
<activity android:name=".AuthActivity"/>
diff --git a/FillService/res/xml/autofill_service_config.xml b/FillService/res/xml/autofill_service_config.xml
new file mode 100644
index 0000000..dd73a19
--- /dev/null
+++ b/FillService/res/xml/autofill_service_config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 Google Inc.
+
+ 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.
+-->
+
+<autofill-service xmlns:android="http://schemas.android.com/apk/res/android">
+ <compatibility-package android:name="com.android.chrome" android:maxLongVersionCode="1000000000" android:urlBarResourceId="url_bar"/>
+ <compatibility-package android:name="com.chrome.beta" android:maxLongVersionCode="1000000000" android:urlBarResourceId="url_bar"/>
+</autofill-service>
diff --git a/FillService/src/foo/bar/fill/FillService.java b/FillService/src/foo/bar/fill/FillService.java
index c253e7c..6eebac1 100644
--- a/FillService/src/foo/bar/fill/FillService.java
+++ b/FillService/src/foo/bar/fill/FillService.java
@@ -24,6 +24,7 @@ import android.app.assist.AssistStructure.WindowNode;
import android.app.assist.AssistStructure.ViewNode;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.PackageManager;
import android.os.CancellationSignal;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
@@ -33,16 +34,24 @@ import android.service.autofill.FillResponse;
import android.service.autofill.SaveCallback;
import android.service.autofill.SaveInfo;
import android.service.autofill.SaveRequest;
+import android.util.Log;
import android.view.View;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
+import android.widget.EditText;
import android.widget.RemoteViews;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Predicate;
+import android.widget.TextView;
import foo.bar.fill.R;
public class FillService extends AutofillService {
+ private static final String LOG_TAG = "FillService";
+
static final boolean TEST_RESPONSE_AUTH = false;
public static final String RESPONSE_ID = "RESPONSE_ID";
@@ -76,8 +85,20 @@ public class FillService extends AutofillService {
@NonNull FillCallback callback) {
AssistStructure structure = request.getFillContexts().get(0).getStructure();
- ViewNode username = findUsername(structure);
- ViewNode password = findPassword(structure);
+ dumpNodeTree(structure);
+
+// ViewNode username = findUsername(structure);
+// ViewNode password = findPassword(structure);
+
+ ViewNode username = null;
+ ViewNode password = null;
+ final List<ViewNode> inputs = findTextInputs(structure);
+ if (inputs.size() > 1) {
+ username = inputs.get(0);
+ password = inputs.get(1);
+ }
+
+ Log.i(LOG_TAG, "found username+username:" + (username != null && password != null));
if (username != null && password != null) {
final FillResponse response;
@@ -134,46 +155,48 @@ public class FillService extends AutofillService {
.setValue(password.getAutofillId(),
AutofillValue.forText(DATASET1_PASSWORD))
.build())
- .addDataset(new Dataset.Builder(presentation2)
- .setValue(username.getAutofillId(),
- AutofillValue.forText(DATASET2_USERNAME))
- .setValue(password.getAutofillId(),
- AutofillValue.forText(DATASET2_PASSWORD))
-// .setAuthentication(sender)
- .build())
- .addDataset(new Dataset.Builder(presentation3)
- .setValue(username.getAutofillId(),
- AutofillValue.forText(DATASET3_USERNAME))
- .setValue(password.getAutofillId(),
- AutofillValue.forText(DATASET3_PASSWORD))
-// .setAuthentication(sender)
- .build())
- .addDataset(new Dataset.Builder(presentation4)
- .setValue(username.getAutofillId(),
- AutofillValue.forText(DATASET4_USERNAME))
- .setValue(password.getAutofillId(),
- AutofillValue.forText(DATASET4_PASSWORD))
+// .addDataset(new Dataset.Builder(presentation2)
+// .setValue(username.getAutofillId(),
+// AutofillValue.forText(DATASET2_USERNAME))
+// .setValue(password.getAutofillId(),
+// AutofillValue.forText(DATASET2_PASSWORD))
+//// .setAuthentication(sender)
+// .build())
+// .addDataset(new Dataset.Builder(presentation3)
+// .setValue(username.getAutofillId(),
+// AutofillValue.forText(DATASET3_USERNAME))
+// .setValue(password.getAutofillId(),
+// AutofillValue.forText(DATASET3_PASSWORD))
+//// .setAuthentication(sender)
+// .build())
+// .addDataset(new Dataset.Builder(presentation4)
+// .setValue(username.getAutofillId(),
+// AutofillValue.forText(DATASET4_USERNAME))
+// .setValue(password.getAutofillId(),
+// AutofillValue.forText(DATASET4_PASSWORD))
+//// .setAuthentication(sender)
+// .build())
+// .addDataset(new Dataset.Builder(presentation5)
+// .setValue(username.getAutofillId(),
+// AutofillValue.forText(DATASET5_USERNAME))
+// .setValue(password.getAutofillId(),
+// AutofillValue.forText(DATASET5_PASSWORD))
// .setAuthentication(sender)
- .build())
- .addDataset(new Dataset.Builder(presentation5)
- .setValue(username.getAutofillId(),
- AutofillValue.forText(DATASET5_USERNAME))
- .setValue(password.getAutofillId(),
- AutofillValue.forText(DATASET5_PASSWORD))
- .setAuthentication(sender)
- .build())
+// .build())
.setSaveInfo(new SaveInfo.Builder(
SaveInfo.SAVE_DATA_TYPE_PASSWORD
| SaveInfo.SAVE_DATA_TYPE_USERNAME,
new AutofillId[] {username.getAutofillId(),
password.getAutofillId()})
+ .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
.build())
.build();
}
callback.onSuccess(response);
} else {
- callback.onFailure("Whoops");
+ //callback.onFailure("Whoops");
+ callback.onSuccess(null);
}
}
@@ -184,20 +207,51 @@ public class FillService extends AutofillService {
ViewNode password = findPassword(structure);
}
+ static void dumpNodeTree(AssistStructure structure) {
+ findByPredicate(structure, (node) -> {
+ if (node.getAutofillValue() != null) {
+ Log.e("class:" + LOG_TAG, node.getClassName() + " value:" + node.getAutofillValue());
+ }
+// Log.e(LOG_TAG, (node.getAutofillValue() != null && node.getAutofillValue().isText())
+// ? node.getAutofillValue().getTextValue().toString() + "-" +node.getAutofillId() : "NOPE");
+ return false;
+ });
+ }
+
+ List<ViewNode > findTextInputs(AssistStructure structure) {
+ final List<ViewNode> inputs = new ArrayList<>();
+ findByPredicate(structure, (node) -> {
+ if (node.getClassName().equals(EditText.class.getName())) {
+ inputs.add(node);
+ }
+ return false;
+ });
+ return inputs;
+ }
+
static ViewNode findUsername(AssistStructure structure) {
return findByPredicate(structure, (node) ->
node.getAutofillType() == View.AUTOFILL_TYPE_TEXT
- && "username".equals(node.getIdEntry())
+ && (autofillTextValueContains(node, "username")
+ || "username".equals(node.getIdEntry()))
);
}
static ViewNode findPassword(AssistStructure structure) {
return findByPredicate(structure, (node) ->
- node.getAutofillType() == View.AUTOFILL_TYPE_TEXT
- && "password".equals(node.getIdEntry())
+ node.getAutofillType() == View.AUTOFILL_TYPE_TEXT
+ && (autofillTextValueContains(node, "password")
+ || "password".equals(node.getIdEntry()))
);
}
+ private static boolean autofillTextValueContains(ViewNode node, String text) {
+ return node.getAutofillValue() != null
+ && node.getAutofillValue().getTextValue() != null
+ && node.getAutofillValue().getTextValue().toString().toLowerCase()
+ .contains(text.toLowerCase());
+ }
+
private static ViewNode findByPredicate(AssistStructure structure,
Predicate<ViewNode> predicate) {
final int windowCount = structure.getWindowNodeCount();
diff --git a/KBars/Android.mk b/KBars/Android.mk
new file mode 100644
index 0000000..c79f7ac
--- /dev/null
+++ b/KBars/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2012 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+#LOCAL_AAPT_FLAGS += -c mdpi,hdpi,xhdpi,xxhdpi,xxhdpi
+
+LOCAL_SRC_FILES := $(call all-java-files-under, app/src/main/java)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/src/main/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v4
+
+LOCAL_PACKAGE_NAME := KBars
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/KBars/app/build.gradle b/KBars/app/build.gradle
new file mode 100644
index 0000000..eee6df5
--- /dev/null
+++ b/KBars/app/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.application'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 26
+ buildToolsVersion "26.0.0"
+ defaultConfig {
+ applicationId "js.kbars"
+ minSdkVersion 19
+ targetSdkVersion 26
+ versionCode 100
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
+ implementation 'com.android.support:appcompat-v7:25.4.0'
+ testImplementation 'junit:junit:4.12'
+}
diff --git a/KBars/app/src/main/AndroidManifest.xml b/KBars/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f330c7b
--- /dev/null
+++ b/KBars/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="js.kbars">
+ <uses-feature android:name="android.hardware.camera" />
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="26" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@drawable/ic_launcher" android:debuggable="true" android:allowBackup="true">
+ <activity android:label="@string/app_name" android:name=".KBarsActivity" android:launchMode="singleTask">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:label="DropShadowActivity" android:name=".DropShadowActivity">
+ </activity>
+ <activity android:label="(kbars-toast)" android:name=".ToastActivity" android:launchMode="singleTask">
+ </activity>
+ <service android:label="(kbars-dream)" android:name=".KBarsDream" android:exported="true">
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </service>
+ <activity android:label="(kbars-car)" android:name=".KBarsCar" android:exported="true" android:launchMode="singleTask">
+ <meta-data android:name="android.dock_home" android:value="true" />
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.CAR_DOCK" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/KBars/app/src/main/assets/background.png b/KBars/app/src/main/assets/background.png
new file mode 100644
index 0000000..aae8a33
--- /dev/null
+++ b/KBars/app/src/main/assets/background.png
Binary files differ
diff --git a/KBars/app/src/main/java/js/kbars/CameraBackgroundMenuItem.java b/KBars/app/src/main/java/js/kbars/CameraBackgroundMenuItem.java
new file mode 100644
index 0000000..8d25d34
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/CameraBackgroundMenuItem.java
@@ -0,0 +1,102 @@
+package js.kbars;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.View;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public final class CameraBackgroundMenuItem implements OnMenuItemClickListener {
+ public static final int REQUEST_CODE = 123;
+ private final Activity mActivity;
+ private final View mTarget;
+
+ public CameraBackgroundMenuItem(Menu menu, Activity activity, View target) {
+ this.mActivity = activity;
+ this.mTarget = target;
+ menu.add("Camera image background...").setOnMenuItemClickListener(this);
+ loadBitmap();
+ }
+
+ public boolean onMenuItemClick(MenuItem item) {
+ chooseBackground();
+ return true;
+ }
+
+ public void onActivityResult(int resultCode, Intent data) {
+ if (resultCode == -1) {
+ Bitmap bitmap = (Bitmap) data.getParcelableExtra("data");
+ if (bitmap != null) {
+ setTargetBackground(bitmap);
+ saveBitmap(bitmap);
+ }
+ }
+ }
+
+ private void chooseBackground() {
+ this.mActivity.startActivityForResult(new Intent("android.media.action.IMAGE_CAPTURE"), REQUEST_CODE);
+ }
+
+ private File bitmapFile() {
+ return new File(this.mActivity.getCacheDir(), "background.png");
+ }
+
+ private void saveBitmap(Bitmap bitmap) {
+ FileNotFoundException e;
+ Throwable th;
+ File f = bitmapFile();
+ if (f.exists()) {
+ f.delete();
+ }
+ if (bitmap != null) {
+ FileOutputStream out = null;
+ try {
+ FileOutputStream out2 = new FileOutputStream(f);
+ bitmap.compress(CompressFormat.PNG, 100, out2);
+ out2.close();
+ } catch (FileNotFoundException e1) {
+ e1.printStackTrace();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
+
+ private void loadBitmap() {
+ Bitmap bitmap = loadBitmapFromCache();
+ if (bitmap == null) {
+ bitmap = loadBitmapFromAssets();
+ }
+ setTargetBackground(bitmap);
+ }
+
+ private Bitmap loadBitmapFromCache() {
+ File f = bitmapFile();
+ return f.exists() ? BitmapFactory.decodeFile(f.getAbsolutePath()) : null;
+ }
+
+ private Bitmap loadBitmapFromAssets() {
+ try {
+ return BitmapFactory.decodeStream(this.mActivity.getAssets().open("background.png"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void setTargetBackground(Bitmap bitmap) {
+ if (bitmap == null) {
+ this.mTarget.setBackground(null);
+ } else {
+ this.mTarget.setBackground(new BitmapDrawable(this.mActivity.getResources(), bitmap));
+ }
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/DropShadowActivity.java b/KBars/app/src/main/java/js/kbars/DropShadowActivity.java
new file mode 100644
index 0000000..79577d9
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/DropShadowActivity.java
@@ -0,0 +1,40 @@
+package js.kbars;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BlurMaskFilter;
+import android.graphics.BlurMaskFilter.Blur;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+
+public class DropShadowActivity extends Activity {
+ private final Context mContext = this;
+ private ImageView mImageView;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.mImageView = new ImageView(this.mContext);
+ this.mImageView.setBackgroundColor(-1);
+ this.mImageView.setScaleType(ScaleType.CENTER);
+ this.mImageView.setScaleX(1.0f);
+ this.mImageView.setScaleY(1.0f);
+ setContentView(this.mImageView);
+ setImage();
+ }
+
+ private void setImage() {
+ BlurMaskFilter blurFilter = new BlurMaskFilter(1.0f, Blur.SOLID);
+ Paint shadowPaint = new Paint();
+ int[] offsetXY = new int[2];
+ Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), 17301624);
+ Bitmap shadowImage32 = originalBitmap.extractAlpha(shadowPaint, offsetXY).copy(Config.ARGB_8888, true);
+ new Canvas(shadowImage32).drawBitmap(originalBitmap, (float) ((-offsetXY[0]) - 10), (float) ((-offsetXY[1]) - 10), null);
+ this.mImageView.setImageBitmap(shadowImage32);
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/FitSystemWindowsActivity.java b/KBars/app/src/main/java/js/kbars/FitSystemWindowsActivity.java
new file mode 100644
index 0000000..35ff6ae
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/FitSystemWindowsActivity.java
@@ -0,0 +1,184 @@
+package js.kbars;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnSystemUiVisibilityChangeListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class FitSystemWindowsActivity extends Activity {
+ private static final int SYSTEM_UI_CLEARABLE_FLAGS = 7;
+ private static final int SYSTEM_UI_FLAG_ALLOW_TRANSIENT = 2048;
+ private static final int SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION = 8192;
+ private static final int SYSTEM_UI_FLAG_TRANSPARENT_STATUS = 4096;
+ private final Context mContext = this;
+ private FlagStateTextView mFlagStateTextView;
+ private final Handler mHandler = new Handler();
+ private LinearLayout mLayout;
+ private final Runnable mUpdateText = new Runnable() {
+ public void run() {
+ FitSystemWindowsActivity.this.mFlagStateTextView.updateText();
+ }
+ };
+
+ private static class Flag implements Comparable<Flag> {
+ public static Flag[] ALL = find();
+ public String caption;
+ public int value;
+
+ private Flag() {
+ }
+
+ private static Flag[] find() {
+ List<Flag> flags = new ArrayList();
+ String prefix = "SYSTEM_UI_FLAG_";
+ for (Field f : View.class.getFields()) {
+ if (f.getName().startsWith(prefix)) {
+ Flag flag = new Flag();
+ flag.caption = f.getName().substring(prefix.length()).replace("NAVIGATION", "NAV").replace("TRANSPARENT", "TRANSP").replace("LAYOUT", "LAY");
+ try {
+ flag.value = f.getInt(null);
+ if (flag.value != 0) {
+ flags.add(flag);
+ }
+ } catch (Throwable t) {
+ RuntimeException runtimeException = new RuntimeException(t);
+ }
+ }
+ }
+ Collections.sort(flags);
+ return (Flag[]) flags.toArray(new Flag[flags.size()]);
+ }
+
+ public int compareTo(Flag another) {
+ if (this.value < another.value) {
+ return -1;
+ }
+ return this.value > another.value ? 1 : 0;
+ }
+ }
+
+ private class FlagCheckBox extends CheckBox {
+ private final Flag mFlag;
+
+ public FlagCheckBox(Context context, Flag flag) {
+ super(context);
+ setText(flag.caption);
+ this.mFlag = flag;
+ }
+ }
+
+ private class FlagSetButton extends Button {
+ public FlagSetButton(Context context) {
+ super(context);
+ setText("Set visibility");
+ setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ FlagSetButton.this.set();
+ }
+ });
+ }
+
+ private void set() {
+ ViewGroup parent = (ViewGroup) getParent();
+ int n = parent.getChildCount();
+ int vis = 0;
+ for (int i = 0; i < n; i++) {
+ View v = parent.getChildAt(i);
+ if (v instanceof FlagCheckBox) {
+ FlagCheckBox cb = (FlagCheckBox) v;
+ if (cb.isChecked()) {
+ vis |= cb.mFlag.value;
+ }
+ }
+ }
+ FitSystemWindowsActivity.this.update(vis);
+ }
+ }
+
+ private class FlagStateTextView extends TextView {
+ public FlagStateTextView(Context context) {
+ super(context);
+ updateText();
+ setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ FlagStateTextView.this.updateText();
+ }
+ });
+ }
+
+ private void updateText() {
+ StringBuilder sb = new StringBuilder();
+ append(sb, "vis", FitSystemWindowsActivity.this.mLayout.getSystemUiVisibility());
+ append(sb, "win", getWindowSystemUiVisibility());
+ setText(sb.toString());
+ }
+
+ private void append(StringBuilder sb, String caption, int vis) {
+ if (sb.length() > 0) {
+ sb.append('\n');
+ }
+ sb.append(caption).append(':');
+ boolean first = true;
+ for (Flag flag : Flag.ALL) {
+ if ((flag.value & vis) != 0) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append(flag.caption);
+ }
+ }
+ }
+ }
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.mLayout = new LinearLayout(this.mContext) {
+ protected boolean fitSystemWindows(Rect insets) {
+ Rect insetsBefore = new Rect(insets);
+ String msg = String.format("before=%s\nrt=%s\nafter=%s", new Object[]{insetsBefore.toShortString(), Boolean.valueOf(super.fitSystemWindows(insets)), insets.toShortString()});
+ return super.fitSystemWindows(insets);
+ }
+ };
+ this.mLayout.setOrientation(1);
+ for (Flag flag : Flag.ALL) {
+ this.mLayout.addView(new FlagCheckBox(this.mContext, flag));
+ }
+ this.mLayout.addView(new FlagSetButton(this.mContext));
+ LinearLayout linearLayout = this.mLayout;
+ View flagStateTextView = new FlagStateTextView(this.mContext);
+ this.mFlagStateTextView = (FlagStateTextView) flagStateTextView;
+ linearLayout.addView(flagStateTextView);
+ this.mLayout.setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
+ public void onSystemUiVisibilityChange(int vis) {
+ FitSystemWindowsActivity.this.updateTextDelayed();
+ }
+ });
+ this.mLayout.setPadding(0, 146, 0, 0);
+ setContentView(this.mLayout);
+ }
+
+ private void update(int vis) {
+ this.mLayout.setSystemUiVisibility(vis);
+ updateTextDelayed();
+ }
+
+ private void updateTextDelayed() {
+ this.mHandler.removeCallbacks(this.mUpdateText);
+ this.mHandler.postDelayed(this.mUpdateText, 500);
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/IdentifyBarsButton.java b/KBars/app/src/main/java/js/kbars/IdentifyBarsButton.java
new file mode 100644
index 0000000..18223d0
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/IdentifyBarsButton.java
@@ -0,0 +1,21 @@
+package js.kbars;
+
+import android.content.Context;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class IdentifyBarsButton extends Button {
+ public IdentifyBarsButton(Context context) {
+ super(context);
+ setText("Identify system bars");
+ setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ IdentifyBarsButton.this.identifyBars();
+ }
+ });
+ }
+
+ private void identifyBars() {
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/ImageBackgroundMenuItem.java b/KBars/app/src/main/java/js/kbars/ImageBackgroundMenuItem.java
new file mode 100644
index 0000000..8052d9e
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/ImageBackgroundMenuItem.java
@@ -0,0 +1,109 @@
+package js.kbars;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.View;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public final class ImageBackgroundMenuItem implements OnMenuItemClickListener {
+ public static final int REQUEST_CODE = 234;
+ private static final String TAG = ImageBackgroundMenuItem.class.getSimpleName();
+ private final Activity mActivity;
+ private final View mTarget;
+
+ public ImageBackgroundMenuItem(Menu menu, Activity activity, View target) {
+ this.mActivity = activity;
+ this.mTarget = target;
+ menu.add("Select image background...").setOnMenuItemClickListener(this);
+ loadBitmap();
+ }
+
+ public boolean onMenuItemClick(MenuItem item) {
+ chooseBackground();
+ return true;
+ }
+
+ public void onActivityResult(int resultCode, Intent data) {
+ if (resultCode == -1) {
+ Uri image = data.getData();
+ try {
+ Bitmap bitmap = BitmapFactory.decodeStream(this.mActivity.getContentResolver().openInputStream(image));
+ if (bitmap != null) {
+ setTargetBackground(bitmap);
+ saveBitmap(bitmap);
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Error getting image " + image, e);
+ }
+ }
+ }
+
+ private void chooseBackground() {
+ Intent photoPickerIntent = new Intent("android.intent.action.PICK");
+ photoPickerIntent.setType("image/*");
+ this.mActivity.startActivityForResult(photoPickerIntent, REQUEST_CODE);
+ }
+
+ private File bitmapFile() {
+ return new File(this.mActivity.getCacheDir(), "background.png");
+ }
+
+ private void saveBitmap(Bitmap bitmap) {
+ FileNotFoundException e;
+ Throwable th;
+ File f = bitmapFile();
+ if (f.exists()) {
+ f.delete();
+ }
+ if (bitmap != null) {
+ try {
+ FileOutputStream out = new FileOutputStream(f);
+ bitmap.compress(CompressFormat.PNG, 100, out);
+ out.close();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
+
+ private void loadBitmap() {
+ Bitmap bitmap = loadBitmapFromCache();
+ if (bitmap == null) {
+ bitmap = loadBitmapFromAssets();
+ }
+ setTargetBackground(bitmap);
+ }
+
+ private Bitmap loadBitmapFromCache() {
+ File f = bitmapFile();
+ return f.exists() ? BitmapFactory.decodeFile(f.getAbsolutePath()) : null;
+ }
+
+ private Bitmap loadBitmapFromAssets() {
+ try {
+ return BitmapFactory.decodeStream(this.mActivity.getAssets().open("background.png"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void setTargetBackground(Bitmap bitmap) {
+ if (bitmap == null) {
+ this.mTarget.setBackground(null);
+ } else {
+ this.mTarget.setBackground(new BitmapDrawable(this.mActivity.getResources(), bitmap));
+ }
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/ImmersiveModeToggleButton.java b/KBars/app/src/main/java/js/kbars/ImmersiveModeToggleButton.java
new file mode 100644
index 0000000..a904b37
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/ImmersiveModeToggleButton.java
@@ -0,0 +1,79 @@
+package js.kbars;
+
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnSystemUiVisibilityChangeListener;
+import android.widget.Button;
+
+public final class ImmersiveModeToggleButton extends Button {
+ private final KBarsActivity mActivity;
+ private final String mCaption;
+ private final int mImmersiveFlags;
+ private boolean mImmersiveMode;
+
+ public ImmersiveModeToggleButton(KBarsActivity activity, String caption, int immersiveFlags) {
+ super(activity);
+ this.mActivity = activity;
+ this.mCaption = caption;
+ this.mImmersiveFlags = immersiveFlags;
+ setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ ImmersiveModeToggleButton.this.toggleImmersiveMode("clicked");
+ }
+ });
+ updateImmersiveModeButton();
+ setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
+ public void onSystemUiVisibilityChange(int visibility) {
+ ImmersiveModeToggleButton.this.onSysuiChanged(visibility);
+ }
+ });
+ }
+
+ private void toggleImmersiveMode(String reason) {
+ boolean z;
+ boolean z2 = false;
+ Log.d(KBarsActivity.TAG, "toggleImmersiveMode reason=" + reason);
+ if (this.mImmersiveMode) {
+ z = false;
+ } else {
+ z = true;
+ }
+ this.mImmersiveMode = z;
+ updateImmersiveModeButton();
+ KBarsActivity kBarsActivity = this.mActivity;
+ if (!this.mImmersiveMode) {
+ z2 = true;
+ }
+ kBarsActivity.setSoloButton(z2, this, true);
+ }
+
+ private void updateImmersiveModeButton() {
+ Log.d(KBarsActivity.TAG, "updateButtons mImmersiveMode=" + this.mImmersiveMode);
+ setText(this.mImmersiveMode ? "Exit " + this.mCaption + " mode" : "Enter " + this.mCaption + " mode");
+ setSystemUiVisibility(this.mImmersiveMode ? this.mImmersiveFlags : KBarsActivity.BASE_FLAGS);
+ }
+
+ private void onSysuiChanged(int vis) {
+ boolean hideStatus;
+ boolean hideSomething = false;
+ Log.d(KBarsActivity.TAG, "sysui changed: " + Integer.toHexString(vis));
+ if ((vis & 4) != 0) {
+ hideStatus = true;
+ } else {
+ hideStatus = false;
+ }
+ boolean hideNav;
+ if ((vis & 2) != 0) {
+ hideNav = true;
+ } else {
+ hideNav = false;
+ }
+ if (hideStatus || hideNav) {
+ hideSomething = true;
+ }
+ if (this.mImmersiveMode && !hideSomething) {
+ toggleImmersiveMode("sysui_changed");
+ }
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/KBarsActivity.java b/KBars/app/src/main/java/js/kbars/KBarsActivity.java
new file mode 100644
index 0000000..33733ac
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/KBarsActivity.java
@@ -0,0 +1,273 @@
+package js.kbars;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.DisplayMetrics;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+import java.util.ArrayList;
+import java.util.List;
+
+public class KBarsActivity extends Activity {
+ public static final int BASE_FLAGS = 1792;
+ static final boolean DEBUG = true;
+ public static final int FLAG_TRANSLUCENT_NAVIGATION = 134217728;
+ public static final int FLAG_TRANSLUCENT_STATUS = 67108864;
+ private static final int IMMERSIVE_FLAGS = 3846;
+ private static final int IMMERSIVE_FLAGS_STICKY = 5894;
+ private static final int SYSTEM_UI_FLAG_IMMERSIVE = 2048;
+ private static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 4096;
+ static final String TAG = Util.logTag(KBarsActivity.class);
+ private List<View> mButtons = new ArrayList();
+ private CameraBackgroundMenuItem mCameraBackgroundMenuItem;
+ private final Context mContext = this;
+ private EditText mEditText;
+ private TouchTrackingLayout mFrame;
+ private ImageBackgroundMenuItem mImageBackgroundMenuItem;
+
+ private static final class DebugButton extends Button {
+ private final Handler mHandler = new Handler();
+ private final Runnable mNavTransOff = new Runnable() {
+ public void run() {
+ DebugButton.this.setNavTrans(false);
+ }
+ };
+ private final Runnable mNavTransOn = new Runnable() {
+ public void run() {
+ DebugButton.this.setNavTrans(true);
+ DebugButton.this.cancelTrans();
+ DebugButton.this.mHandler.postDelayed(DebugButton.this.mNavTransOff, 5000);
+ }
+ };
+
+ public DebugButton(Context context) {
+ super(context);
+ setText("Debug");
+ setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ DebugButton.this.fallingToast();
+ }
+ });
+ }
+
+ private void cancelTrans() {
+ this.mHandler.removeCallbacks(this.mNavTransOn);
+ this.mHandler.removeCallbacks(this.mNavTransOff);
+ }
+
+ private void setNavTrans(boolean trans) {
+ Window w = ((Activity) getContext()).getWindow();
+ if (trans) {
+ w.addFlags(KBarsActivity.FLAG_TRANSLUCENT_STATUS);
+ } else {
+ w.clearFlags(KBarsActivity.FLAG_TRANSLUCENT_STATUS);
+ }
+ }
+
+ public void fallingToast() {
+ this.mNavTransOff.run();
+ Toast.makeText(getContext(), "Here is a toast", 1).show();
+ cancelTrans();
+ this.mHandler.postDelayed(this.mNavTransOn, 1000);
+ }
+ }
+
+ private static final class InvisibleButton extends Button {
+ public InvisibleButton(Context context) {
+ super(context);
+ super.setVisibility(4);
+ }
+
+ public void setVisibility(int visibility) {
+ }
+ }
+
+ protected void onCreate(Bundle savedInstanceState) {
+ boolean portrait;
+ int i = 0;
+ super.onCreate(savedInstanceState);
+ getWindow().requestFeature(9);
+ DisplayMetrics dm = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getRealMetrics(dm);
+ Toast.makeText(this.mContext, dm.widthPixels + "x" + dm.heightPixels + " supported=" + areTranslucentBarsSupported(), 0).show();
+ getActionBar().setTitle("kbars " + Util.getVersionName(this.mContext));
+ getActionBar().setBackgroundDrawable(new ColorDrawable(0));
+ this.mFrame = new TouchTrackingLayout(this.mContext);
+ LinearLayout allButtons = new LinearLayout(this.mContext);
+ Configuration config = getResources().getConfiguration();
+ if (((float) config.screenWidthDp) / ((float) config.screenHeightDp) < 1.0f) {
+ portrait = true;
+ } else {
+ portrait = false;
+ }
+ if (portrait) {
+ i = 1;
+ }
+ allButtons.setOrientation(i);
+ LinearLayout buttons1 = new LinearLayout(this.mContext);
+ buttons1.setOrientation(1);
+ LinearLayout buttons2 = new LinearLayout(this.mContext);
+ buttons2.setOrientation(1);
+ allButtons.addView(buttons1);
+ allButtons.addView(buttons2);
+ LayoutParams buttonsLP = new LayoutParams(-2, -2);
+ buttonsLP.gravity = 17;
+ this.mFrame.addView(allButtons, buttonsLP);
+ this.mEditText = new EditText(this.mContext);
+ this.mEditText.setVisibility(8);
+ buttons1.addView(this.mEditText, -1, -2);
+ buttons1.addView(new TransparencyToggleButton(this.mContext, "status bar", FLAG_TRANSLUCENT_STATUS));
+ buttons1.addView(new TransparencyToggleButton(this.mContext, "navigation bar", FLAG_TRANSLUCENT_NAVIGATION));
+ buttons1.addView(new InvisibleButton(this.mContext));
+ buttons2.addView(new ImmersiveModeToggleButton(this, "immersive", IMMERSIVE_FLAGS));
+ buttons2.addView(new ImmersiveModeToggleButton(this, "sticky immersive", IMMERSIVE_FLAGS_STICKY));
+ buttons2.addView(new LightsOutModeToggleButton(this.mContext));
+ buttons2.addView(new MediaModeToggleButton(this.mContext, this.mFrame));
+ buttons2.addView(new InvisibleButton(this.mContext));
+ setContentView(this.mFrame);
+ addButtons(buttons1);
+ addButtons(buttons2);
+ }
+
+ private void addButtons(LinearLayout buttons) {
+ for (int i = 0; i < buttons.getChildCount(); i++) {
+ this.mButtons.add(buttons.getChildAt(i));
+ }
+ }
+
+ private boolean areTranslucentBarsSupported() {
+ int id = getResources().getIdentifier("config_enableTranslucentDecor", "bool", "android");
+ if (id == 0) {
+ return false;
+ }
+ return getResources().getBoolean(id);
+ }
+
+ public boolean onCreateOptionsMenu(Menu menu) {
+ RandomColorBackgroundMenuItem randomColorBackgroundMenuItem = new RandomColorBackgroundMenuItem(menu, this.mFrame);
+ this.mImageBackgroundMenuItem = new ImageBackgroundMenuItem(menu, this, this.mFrame);
+ this.mCameraBackgroundMenuItem = new CameraBackgroundMenuItem(menu, this, this.mFrame);
+ createDebugMenuItem(menu);
+ createEditTextMenuItem(menu);
+ createOrientationMenuItem(menu, R.string.action_portrait, 1);
+ createOrientationMenuItem(menu, R.string.action_landscape, 0);
+ createOrientationMenuItem(menu, R.string.action_reverse_portrait, 9);
+ createOrientationMenuItem(menu, R.string.action_reverse_landscape, 8);
+ return true;
+ }
+
+ private void createOrientationMenuItem(Menu menu, int text, final int orientation) {
+ MenuItem mi = menu.add(text);
+ mi.setShowAsAction(0);
+ mi.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ KBarsActivity.this.setRequestedOrientation(orientation);
+ return true;
+ }
+ });
+ }
+
+ private void createDebugMenuItem(Menu menu) {
+ final MenuItem mi = menu.add("Show gesture debugging");
+ mi.setShowAsActionFlags(0);
+ mi.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ boolean debug;
+ if (KBarsActivity.this.mFrame.getDebug()) {
+ debug = false;
+ } else {
+ debug = true;
+ }
+ KBarsActivity.this.mFrame.setDebug(debug);
+ mi.setTitle(new StringBuilder(String.valueOf(debug ? "Hide" : "Show")).append(" gesture debugging").toString());
+ return true;
+ }
+ });
+ }
+
+ private void createEditTextMenuItem(Menu menu) {
+ final MenuItem mi = menu.add("Show EditText");
+ mi.setShowAsActionFlags(0);
+ mi.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ boolean isVisible;
+ int i = 0;
+ if (KBarsActivity.this.mEditText.getVisibility() == 0) {
+ isVisible = true;
+ } else {
+ isVisible = false;
+ }
+ EditText access$1 = KBarsActivity.this.mEditText;
+ if (isVisible) {
+ i = 8;
+ }
+ access$1.setVisibility(i);
+ mi.setTitle(new StringBuilder(String.valueOf(isVisible ? "Show" : "Hide")).append(" EditText").toString());
+ return true;
+ }
+ });
+ }
+
+ public void setSoloButton(boolean visible, View solo, boolean animate) {
+ int i;
+ int vis = 0;
+ if (visible) {
+ i = 1;
+ } else {
+ i = 0;
+ }
+ float alpha = (float) i;
+ if (!visible) {
+ vis = 4;
+ }
+ final int _vis = vis;
+ for (final View view : this.mButtons) {
+ if (!(view == solo || (view instanceof EditText))) {
+ view.setEnabled(visible);
+ if (animate) {
+ view.animate().alpha(alpha).setListener(new AnimatorListener() {
+ public void onAnimationCancel(Animator animation) {
+ }
+
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(_vis);
+ }
+
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ public void onAnimationStart(Animator animation) {
+ }
+ }).start();
+ } else {
+ view.setVisibility(vis);
+ }
+ }
+ }
+ }
+
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == CameraBackgroundMenuItem.REQUEST_CODE) {
+ this.mCameraBackgroundMenuItem.onActivityResult(resultCode, data);
+ }
+ if (requestCode == ImageBackgroundMenuItem.REQUEST_CODE) {
+ this.mImageBackgroundMenuItem.onActivityResult(resultCode, data);
+ }
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/KBarsCar.java b/KBars/app/src/main/java/js/kbars/KBarsCar.java
new file mode 100644
index 0000000..c280c95
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/KBarsCar.java
@@ -0,0 +1,38 @@
+package js.kbars;
+
+import android.app.Activity;
+import android.app.UiModeManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+public class KBarsCar extends Activity {
+ private static final String TAG = Util.logTag(KBarsCar.class);
+ private final Context mContext = this;
+ private View mView;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ requestWindowFeature(1);
+ super.onCreate(savedInstanceState);
+ this.mView = new View(this.mContext);
+ this.mView.setBackgroundColor(-65536);
+ setFullscreen();
+ setContentView(this.mView);
+ this.mView.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ ((UiModeManager) KBarsCar.this.mContext.getSystemService("uimode")).disableCarMode(1);
+ KBarsCar.this.finish();
+ }
+ });
+ }
+
+ protected void onResume() {
+ super.onResume();
+ setFullscreen();
+ }
+
+ private void setFullscreen() {
+ this.mView.setSystemUiVisibility(5382);
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/KBarsDream.java b/KBars/app/src/main/java/js/kbars/KBarsDream.java
new file mode 100644
index 0000000..7a4c92b
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/KBarsDream.java
@@ -0,0 +1,47 @@
+package js.kbars;
+
+import android.content.Context;
+import android.os.Handler;
+import android.service.dreams.DreamService;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnSystemUiVisibilityChangeListener;
+
+public class KBarsDream extends DreamService {
+ private static final String TAG = Util.logTag(KBarsDream.class);
+ private final Context mContext = this;
+ private final Handler mHandler = new Handler();
+ private View mView;
+
+ public void onCreate() {
+ super.onCreate();
+ setInteractive(true);
+ }
+
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ this.mView = new View(this.mContext);
+ getWindow().addFlags(1024);
+ setFullscreen();
+ this.mView.setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
+ public void onSystemUiVisibilityChange(int visibility) {
+ Log.d(KBarsDream.TAG, "onSystemUiVisibilityChange " + Integer.toHexString(visibility));
+ KBarsDream.this.setFullscreen();
+ }
+ });
+ this.mView.setBackgroundColor(-16776961);
+ setContentView(this.mView);
+ }
+
+ public void onWindowFocusChanged(boolean hasFocus) {
+ Log.d(TAG, "onWindowFocusChanged " + hasFocus);
+ super.onWindowFocusChanged(hasFocus);
+ if (hasFocus) {
+ setFullscreen();
+ }
+ }
+
+ private void setFullscreen() {
+ this.mView.setSystemUiVisibility(5382);
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/LightsOutModeToggleButton.java b/KBars/app/src/main/java/js/kbars/LightsOutModeToggleButton.java
new file mode 100644
index 0000000..9593142
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/LightsOutModeToggleButton.java
@@ -0,0 +1,26 @@
+package js.kbars;
+
+import android.content.Context;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public final class LightsOutModeToggleButton extends Button {
+ private boolean mLightsOut;
+
+ public LightsOutModeToggleButton(Context context) {
+ super(context);
+ setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ LightsOutModeToggleButton.this.mLightsOut = !LightsOutModeToggleButton.this.mLightsOut;
+ LightsOutModeToggleButton.this.update();
+ }
+ });
+ update();
+ }
+
+ private void update() {
+ setText(new StringBuilder(String.valueOf(this.mLightsOut ? "Exit" : "Enter")).append(" lights out mode").toString());
+ setSystemUiVisibility(this.mLightsOut ? 1 : 0);
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/MediaModeToggleButton.java b/KBars/app/src/main/java/js/kbars/MediaModeToggleButton.java
new file mode 100644
index 0000000..1e2b19d
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/MediaModeToggleButton.java
@@ -0,0 +1,95 @@
+package js.kbars;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnSystemUiVisibilityChangeListener;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.VideoView;
+
+public final class MediaModeToggleButton extends Button {
+ private static final int MEDIA_FLAGS = 1798;
+ private final FrameLayout mFrame;
+ private boolean mMediaMode;
+ private VideoView mVideo;
+
+ public MediaModeToggleButton(Context context, FrameLayout frame) {
+ super(context);
+ this.mFrame = frame;
+ setText("Enter media mode");
+ setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ MediaModeToggleButton.this.setSystemUiVisibility(MediaModeToggleButton.MEDIA_FLAGS);
+ }
+ });
+ setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
+ public void onSystemUiVisibilityChange(int vis) {
+ MediaModeToggleButton.this.updateSysUi(vis);
+ }
+ });
+ initVideo();
+ updateSysUi(0);
+ }
+
+ private void initVideo() {
+ this.mVideo = new VideoView(getContext());
+ this.mVideo.setVisibility(8);
+ LayoutParams videoLP = new LayoutParams(-1, -1);
+ videoLP.gravity = 17;
+ this.mFrame.addView(this.mVideo, videoLP);
+ this.mVideo.setOnPreparedListener(new OnPreparedListener() {
+ public void onPrepared(MediaPlayer mp) {
+ mp.setLooping(true);
+ }
+ });
+ this.mVideo.setVideoURI(Uri.parse("android.resource://" + getContext().getPackageName() + "/" + R.raw.clipcanvas));
+ this.mVideo.setBackgroundColor(0);
+ }
+
+ private void updateSysUi(int vis) {
+ boolean requested;
+ boolean hideStatus;
+ boolean hideNav;
+ boolean hideSomething;
+ if (getSystemUiVisibility() == MEDIA_FLAGS) {
+ requested = true;
+ } else {
+ requested = false;
+ }
+ if ((vis & 4) != 0) {
+ hideStatus = true;
+ } else {
+ hideStatus = false;
+ }
+ if ((vis & 2) != 0) {
+ hideNav = true;
+ } else {
+ hideNav = false;
+ }
+ if (hideStatus || hideNav) {
+ hideSomething = true;
+ } else {
+ hideSomething = false;
+ }
+ Log.d(KBarsActivity.TAG, String.format("vis change hideStatus=%s hideNav=%s hideSomething=%s mMediaMode=%s", new Object[]{Boolean.valueOf(hideStatus), Boolean.valueOf(hideNav), Boolean.valueOf(hideSomething), Boolean.valueOf(this.mMediaMode)}));
+ this.mMediaMode = false;
+ if (requested && hideStatus && hideNav) {
+ this.mMediaMode = true;
+ } else {
+ setSystemUiVisibility(KBarsActivity.BASE_FLAGS);
+ }
+ if (this.mMediaMode) {
+ this.mVideo.setVisibility(0);
+ this.mVideo.start();
+ return;
+ }
+ this.mVideo.setVisibility(8);
+ this.mVideo.stopPlayback();
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/RandomColorBackgroundMenuItem.java b/KBars/app/src/main/java/js/kbars/RandomColorBackgroundMenuItem.java
new file mode 100644
index 0000000..d1895f3
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/RandomColorBackgroundMenuItem.java
@@ -0,0 +1,29 @@
+package js.kbars;
+
+import android.graphics.Color;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.view.View;
+import java.util.Random;
+
+public class RandomColorBackgroundMenuItem implements OnMenuItemClickListener {
+ private static final Random RANDOM = new Random();
+ private final View mTarget;
+
+ public RandomColorBackgroundMenuItem(Menu menu, View target) {
+ this.mTarget = target;
+ MenuItem mi = menu.add("Random color background");
+ mi.setShowAsActionFlags(0);
+ mi.setOnMenuItemClickListener(this);
+ }
+
+ private void setColorBackground() {
+ this.mTarget.setBackgroundColor(Color.rgb(RANDOM.nextInt(255), RANDOM.nextInt(255), RANDOM.nextInt(255)));
+ }
+
+ public boolean onMenuItemClick(MenuItem item) {
+ setColorBackground();
+ return true;
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/SystemGestureDebugger.java b/KBars/app/src/main/java/js/kbars/SystemGestureDebugger.java
new file mode 100644
index 0000000..3992a82
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/SystemGestureDebugger.java
@@ -0,0 +1,216 @@
+package js.kbars;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.MotionEvent;
+
+public class SystemGestureDebugger {
+ private static final boolean DEBUG = false;
+ private static final int MAX_TRACKED_POINTERS = 32;
+ private static final int SWIPE_FROM_BOTTOM = 2;
+ private static final int SWIPE_FROM_RIGHT = 3;
+ private static final int SWIPE_FROM_TOP = 1;
+ private static final int SWIPE_NONE = 0;
+ private static final long SWIPE_TIMEOUT_MS = 500;
+ private static final String TAG = Util.logTag(SystemGestureDebugger.class);
+ private static final int UNTRACKED_POINTER = -1;
+ private final Callbacks mCallbacks;
+ private final float[] mCurrentX = new float[MAX_TRACKED_POINTERS];
+ private final float[] mCurrentY = new float[MAX_TRACKED_POINTERS];
+ private boolean mDebugFireable;
+ private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
+ private int mDownPointers;
+ private final long[] mDownTime = new long[MAX_TRACKED_POINTERS];
+ private final float[] mDownX = new float[MAX_TRACKED_POINTERS];
+ private final float[] mDownY = new float[MAX_TRACKED_POINTERS];
+ private final long[] mElapsedThreshold = new long[MAX_TRACKED_POINTERS];
+ private final int mSwipeDistanceThreshold;
+ private boolean mSwipeFireable;
+ private final int mSwipeStartThreshold;
+ int screenHeight;
+ int screenWidth;
+
+ interface Callbacks {
+ void onDebug();
+
+ void onSwipeFromBottom();
+
+ void onSwipeFromRight();
+
+ void onSwipeFromTop();
+ }
+
+ public static class DownPointerInfo {
+ public float currentX;
+ public float currentY;
+ public int downPointerId;
+ public long downTime;
+ public float downX;
+ public float downY;
+ public long elapsedThreshold;
+ }
+
+ public static class ThresholdInfo {
+ public int distance;
+ public long elapsed;
+ public int start;
+ }
+
+ public SystemGestureDebugger(Context context, Callbacks callbacks) {
+ this.mCallbacks = (Callbacks) checkNull("callbacks", callbacks);
+ this.mSwipeStartThreshold = ((Context) checkNull("context", context)).getResources().getDimensionPixelSize(((Integer) Util.getField("com.android.internal.R$dimen", "status_bar_height")).intValue());
+ this.mSwipeDistanceThreshold = this.mSwipeStartThreshold;
+ Log.d(TAG, String.format("startThreshold=%s endThreshold=%s", new Object[]{Integer.valueOf(this.mSwipeStartThreshold), Integer.valueOf(this.mSwipeDistanceThreshold)}));
+ }
+
+ private static <T> T checkNull(String name, T arg) {
+ if (arg != null) {
+ return arg;
+ }
+ throw new IllegalArgumentException(new StringBuilder(String.valueOf(name)).append(" must not be null").toString());
+ }
+
+ public void onPointerEvent(MotionEvent event) {
+ boolean z = true;
+ boolean z2 = DEBUG;
+ switch (event.getActionMasked()) {
+ case SWIPE_NONE /*0*/:
+ this.mSwipeFireable = true;
+ this.mDebugFireable = true;
+ this.mDownPointers = SWIPE_NONE;
+ captureDown(event, SWIPE_NONE);
+ return;
+ case 1:
+ case SWIPE_FROM_RIGHT /*3*/:
+ this.mSwipeFireable = DEBUG;
+ this.mDebugFireable = DEBUG;
+ return;
+ case 2:
+ if (this.mSwipeFireable) {
+ int swipe = detectSwipe(event);
+ if (swipe == 0) {
+ z2 = true;
+ }
+ this.mSwipeFireable = z2;
+ if (swipe == 1) {
+ this.mCallbacks.onSwipeFromTop();
+ return;
+ } else if (swipe == 2) {
+ this.mCallbacks.onSwipeFromBottom();
+ return;
+ } else if (swipe == SWIPE_FROM_RIGHT) {
+ this.mCallbacks.onSwipeFromRight();
+ return;
+ } else {
+ return;
+ }
+ }
+ return;
+ case 5:
+ captureDown(event, event.getActionIndex());
+ if (this.mDebugFireable) {
+ if (event.getPointerCount() >= 5) {
+ z = DEBUG;
+ }
+ this.mDebugFireable = z;
+ if (!this.mDebugFireable) {
+ this.mCallbacks.onDebug();
+ return;
+ }
+ return;
+ }
+ return;
+ default:
+ return;
+ }
+ }
+
+ private void captureDown(MotionEvent event, int pointerIndex) {
+ int i = findIndex(event.getPointerId(pointerIndex));
+ if (i != UNTRACKED_POINTER) {
+ this.mDownX[i] = event.getX(pointerIndex);
+ this.mDownY[i] = event.getY(pointerIndex);
+ this.mDownTime[i] = event.getEventTime();
+ }
+ }
+
+ private int findIndex(int pointerId) {
+ for (int i = SWIPE_NONE; i < this.mDownPointers; i++) {
+ if (this.mDownPointerId[i] == pointerId) {
+ return i;
+ }
+ }
+ if (this.mDownPointers == MAX_TRACKED_POINTERS || pointerId == UNTRACKED_POINTER) {
+ return UNTRACKED_POINTER;
+ }
+ int[] iArr = this.mDownPointerId;
+ int i2 = this.mDownPointers;
+ this.mDownPointers = i2 + 1;
+ iArr[i2] = pointerId;
+ return this.mDownPointers + UNTRACKED_POINTER;
+ }
+
+ private int detectSwipe(MotionEvent move) {
+ int historySize = move.getHistorySize();
+ int pointerCount = move.getPointerCount();
+ for (int p = SWIPE_NONE; p < pointerCount; p++) {
+ int i = findIndex(move.getPointerId(p));
+ if (i != UNTRACKED_POINTER) {
+ int swipe;
+ for (int h = SWIPE_NONE; h < historySize; h++) {
+ swipe = detectSwipe(i, move.getHistoricalEventTime(h), move.getHistoricalX(p, h), move.getHistoricalY(p, h));
+ if (swipe != 0) {
+ return swipe;
+ }
+ }
+ swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p));
+ if (swipe != 0) {
+ return swipe;
+ }
+ }
+ }
+ return SWIPE_NONE;
+ }
+
+ private int detectSwipe(int i, long time, float x, float y) {
+ float fromX = this.mDownX[i];
+ float fromY = this.mDownY[i];
+ long elapsed = time - this.mDownTime[i];
+ this.mCurrentX[i] = x;
+ this.mCurrentY[i] = y;
+ boolean reachedThreshold = ((fromY > ((float) this.mSwipeStartThreshold) || y <= ((float) this.mSwipeDistanceThreshold) + fromY) && ((fromY < ((float) (this.screenHeight - this.mSwipeStartThreshold)) || y >= fromY - ((float) this.mSwipeDistanceThreshold)) && (fromX < ((float) (this.screenWidth - this.mSwipeStartThreshold)) || x >= fromX - ((float) this.mSwipeDistanceThreshold)))) ? DEBUG : true;
+ if (!reachedThreshold) {
+ this.mElapsedThreshold[i] = elapsed;
+ }
+ if (fromY <= ((float) this.mSwipeStartThreshold) && y > ((float) this.mSwipeDistanceThreshold) + fromY && elapsed < SWIPE_TIMEOUT_MS) {
+ return 1;
+ }
+ if (fromY >= ((float) (this.screenHeight - this.mSwipeStartThreshold)) && y < fromY - ((float) this.mSwipeDistanceThreshold) && elapsed < SWIPE_TIMEOUT_MS) {
+ return 2;
+ }
+ if (fromX < ((float) (this.screenWidth - this.mSwipeStartThreshold)) || x >= fromX - ((float) this.mSwipeDistanceThreshold) || elapsed >= SWIPE_TIMEOUT_MS) {
+ return SWIPE_NONE;
+ }
+ return SWIPE_FROM_RIGHT;
+ }
+
+ public int getDownPointers() {
+ return this.mDownPointers;
+ }
+
+ public void getDownPointerInfo(int index, DownPointerInfo out) {
+ out.downPointerId = this.mDownPointerId[index];
+ out.downX = this.mDownX[index];
+ out.downY = this.mDownY[index];
+ out.downTime = this.mDownTime[index];
+ out.currentX = this.mCurrentX[index];
+ out.currentY = this.mCurrentY[index];
+ out.elapsedThreshold = this.mElapsedThreshold[index];
+ }
+
+ public void getThresholdInfo(ThresholdInfo out) {
+ out.start = this.mSwipeStartThreshold;
+ out.distance = this.mSwipeDistanceThreshold;
+ out.elapsed = SWIPE_TIMEOUT_MS;
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/ToastActivity.java b/KBars/app/src/main/java/js/kbars/ToastActivity.java
new file mode 100644
index 0000000..18f9a89
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/ToastActivity.java
@@ -0,0 +1,117 @@
+package js.kbars;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnSystemUiVisibilityChangeListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+public class ToastActivity extends Activity {
+ private static final int ALLOW_TRANSIENT = 2048;
+ private static final String TAG = Util.logTag(ToastActivity.class);
+ private View mContent;
+ private final Context mContext = this;
+ boolean mImmersive;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout buttons = new LinearLayout(this.mContext);
+ buttons.setOrientation(1);
+ for (final Method m : getClass().getDeclaredMethods()) {
+ if (Modifier.isPublic(m.getModifiers()) && m.getParameterTypes().length == 0) {
+ Button btn = new Button(this.mContext);
+ btn.setText(m.getName());
+ btn.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ try {
+ m.invoke(ToastActivity.this.mContext, new Object[0]);
+ } catch (Throwable t) {
+ Log.w(ToastActivity.TAG, "Error running " + m.getName(), t);
+ }
+ }
+ });
+ buttons.addView(btn);
+ }
+ }
+ setContentView(buttons);
+ this.mContent = buttons;
+ setSysui();
+ this.mContent.setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
+ public void onSystemUiVisibilityChange(int visibility) {
+ if ((visibility & 2) == 0) {
+ ToastActivity.this.mImmersive = false;
+ ToastActivity.this.setSysui();
+ }
+ }
+ });
+ }
+
+ public void toast1() {
+ Toast.makeText(this.mContext, "toast!", 0).show();
+ }
+
+ public void toast2() {
+ Toast t = Toast.makeText(this.mContext, "toast!", 0);
+ TextView tv = new TextView(this.mContext);
+ tv.setBackgroundColor(-65536);
+ tv.setText("setView");
+ t.setView(tv);
+ t.show();
+ }
+
+ public void toast3() {
+ Toast t = Toast.makeText(this.mContext, "toast!", 0);
+ TextView tv = new TextView(this.mContext) {
+ protected boolean fitSystemWindows(Rect insets) {
+ Rect before = new Rect(insets);
+ boolean rt = super.fitSystemWindows(insets);
+ Log.d(ToastActivity.TAG, String.format("before=%s rt=%s after=%s", new Object[]{before.toShortString(), Boolean.valueOf(rt), insets.toShortString()}));
+ return rt;
+ }
+ };
+ Log.d(TAG, "fitsSystemWindows=" + tv.getFitsSystemWindows());
+ tv.setFitsSystemWindows(true);
+ tv.setSystemUiVisibility(768);
+ tv.setBackgroundColor(-65536);
+ tv.setText("setView");
+ t.setView(tv);
+ t.show();
+ }
+
+ public void hideNav() {
+ this.mContent.setSystemUiVisibility(2);
+ }
+
+ public void dangerToast() {
+ Toast t = Toast.makeText(this.mContext, "toast!", 0);
+ TextView tv = new TextView(this.mContext);
+ tv.setSystemUiVisibility(512);
+ tv.setBackgroundColor(-65536);
+ tv.setText("setView");
+ t.setView(tv);
+ t.setGravity(80, 0, 90);
+ t.show();
+ }
+
+ public void toggleImmersive() {
+ this.mImmersive = !this.mImmersive;
+ setSysui();
+ }
+
+ private void setSysui() {
+ int flags = 2560;
+ if (this.mImmersive) {
+ flags = 2560 | 2;
+ }
+ this.mContent.setSystemUiVisibility(flags);
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/TouchTrackingLayout.java b/KBars/app/src/main/java/js/kbars/TouchTrackingLayout.java
new file mode 100644
index 0000000..83e6c30
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/TouchTrackingLayout.java
@@ -0,0 +1,298 @@
+package js.kbars;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Cap;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.widget.FrameLayout;
+import java.util.ArrayList;
+import java.util.List;
+import js.kbars.SystemGestureDebugger.DownPointerInfo;
+import js.kbars.SystemGestureDebugger.ThresholdInfo;
+
+public class TouchTrackingLayout extends FrameLayout {
+ private static final boolean DEBUG_EVENTS = true;
+ private static final int MAX_INFO_LINES = 6;
+ private static final int MAX_POINTERS = 5;
+ private static final int MAX_POINTS = 5000;
+ private static final String TAG = Util.logTag(TouchTrackingLayout.class);
+ private boolean mDebug;
+ private final DownPointerInfo mDownPointerInfo = new DownPointerInfo();
+ private final SystemGestureDebugger mGestures;
+ private final Paint mInfoPaint = new Paint();
+ private final StringBuilder[] mInfoText = allocateInfoText();
+ private final int mInfoTextLineHeight;
+ private final int mInfoTextOffset;
+ private final int[] mScribbleCounts = new int[MAX_POINTERS];
+ private final Paint mScribblePaint = new Paint();
+ private final List<float[]> mScribblePoints = allocatePoints();
+ private final int mTextSize;
+ private final ThresholdInfo mThresholdInfo = new ThresholdInfo();
+ private final float[] mThresholdLines = new float[16];
+ private final Paint mThresholdPaint = new Paint();
+
+ public TouchTrackingLayout(Context context) {
+ super(context);
+ setWillNotDraw(false);
+ int densityDpi = Util.getDensityDpi(context);
+ this.mTextSize = (int) (((float) densityDpi) / 12.0f);
+ this.mInfoTextLineHeight = (int) (((float) densityDpi) / 11.0f);
+ this.mInfoTextOffset = (int) (((float) densityDpi) / 1.7f);
+ Log.d(TAG, "mTextSize=" + this.mTextSize + " mInfoTextLineHeight=" + this.mInfoTextLineHeight + " mInfoTextOffset=" + this.mInfoTextOffset);
+ this.mScribblePaint.setColor(-13388315);
+ this.mScribblePaint.setStrokeWidth((float) this.mTextSize);
+ this.mScribblePaint.setStrokeCap(Cap.ROUND);
+ this.mScribblePaint.setStyle(Style.STROKE);
+ this.mGestures = new SystemGestureDebugger(context, new SystemGestureDebugger.Callbacks() {
+ public void onSwipeFromTop() {
+ Log.d(TouchTrackingLayout.TAG, "GestureCallbacks.onSwipeFromTop");
+ }
+
+ public void onSwipeFromRight() {
+ Log.d(TouchTrackingLayout.TAG, "GestureCallbacks.onSwipeFromRight");
+ }
+
+ public void onSwipeFromBottom() {
+ Log.d(TouchTrackingLayout.TAG, "GestureCallbacks.onSwipeFromBottom");
+ }
+
+ public void onDebug() {
+ Log.d(TouchTrackingLayout.TAG, "GestureCallbacks.onDebug");
+ }
+ });
+ this.mGestures.getThresholdInfo(this.mThresholdInfo);
+ this.mInfoPaint.setTypeface(Typeface.MONOSPACE);
+ this.mInfoPaint.setTextSize((float) this.mTextSize);
+ this.mInfoPaint.setStyle(Style.STROKE);
+ this.mInfoPaint.setColor(-13388315);
+ this.mThresholdPaint.setColor(-13388315);
+ this.mThresholdPaint.setStrokeWidth(1.0f);
+ this.mThresholdPaint.setStyle(Style.STROKE);
+ addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ TouchTrackingLayout.this.mGestures.screenWidth = right - left;
+ TouchTrackingLayout.this.mGestures.screenHeight = bottom - top;
+ TouchTrackingLayout.this.horLine(0, TouchTrackingLayout.this.mThresholdInfo.start);
+ TouchTrackingLayout.this.horLine(1, TouchTrackingLayout.this.mGestures.screenHeight - TouchTrackingLayout.this.mThresholdInfo.start);
+ TouchTrackingLayout.this.verLine(2, TouchTrackingLayout.this.mThresholdInfo.start);
+ TouchTrackingLayout.this.verLine(3, TouchTrackingLayout.this.mGestures.screenWidth - TouchTrackingLayout.this.mThresholdInfo.start);
+ }
+ });
+ }
+
+ public boolean getDebug() {
+ return this.mDebug;
+ }
+
+ public void setDebug(boolean debug) {
+ this.mDebug = debug;
+ invalidate();
+ }
+
+ protected boolean fitSystemWindows(Rect insets) {
+ Rect insetsBefore = new Rect(insets);
+ boolean rt = super.fitSystemWindows(insets);
+ Log.d(TAG, String.format("fitSystemWindows insetsBefore=%s insetsAfter=%s rt=%s", new Object[]{insetsBefore.toShortString(), insets.toShortString(), Boolean.valueOf(rt)}));
+ return rt;
+ }
+
+ private void horLine(int lineNum, int y) {
+ this.mThresholdLines[(lineNum * 4) + 0] = 0.0f;
+ float[] fArr = this.mThresholdLines;
+ int i = (lineNum * 4) + 1;
+ float f = (float) y;
+ this.mThresholdLines[(lineNum * 4) + 3] = f;
+ fArr[i] = f;
+ this.mThresholdLines[(lineNum * 4) + 2] = (float) this.mGestures.screenWidth;
+ }
+
+ private void verLine(int lineNum, int x) {
+ float[] fArr = this.mThresholdLines;
+ int i = (lineNum * 4) + 0;
+ float f = (float) x;
+ this.mThresholdLines[(lineNum * 4) + 2] = f;
+ fArr[i] = f;
+ this.mThresholdLines[(lineNum * 4) + 1] = 0.0f;
+ this.mThresholdLines[(lineNum * 4) + 3] = (float) this.mGestures.screenHeight;
+ }
+
+ private static StringBuilder[] allocateInfoText() {
+ StringBuilder[] rt = new StringBuilder[MAX_INFO_LINES];
+ for (int i = 0; i < rt.length; i++) {
+ rt[i] = new StringBuilder();
+ }
+ return rt;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ super.onTouchEvent(ev);
+ this.mGestures.onPointerEvent(ev);
+ computeInfoText();
+ onMotionEvent(ev);
+ return true;
+ }
+
+ protected void onDraw(Canvas canvas) {
+ int i;
+ for (i = 0; i < this.mScribbleCounts.length; i++) {
+ int c = this.mScribbleCounts[i];
+ if (c > 0) {
+ canvas.drawLines((float[]) this.mScribblePoints.get(i), 0, c - 2, this.mScribblePaint);
+ }
+ }
+ if (this.mDebug) {
+ int leftMargin = this.mThresholdInfo.start + (this.mTextSize / 4);
+ for (i = 0; i < this.mInfoText.length; i++) {
+ StringBuilder sb = this.mInfoText[i];
+ canvas.drawText(sb, 0, sb.length(), (float) leftMargin, (float) (this.mInfoTextOffset + (this.mInfoTextLineHeight * i)), this.mInfoPaint);
+ }
+ canvas.drawLines(this.mThresholdLines, this.mThresholdPaint);
+ }
+ }
+
+ private void computeInfoText() {
+ int dpc = this.mGestures.getDownPointers();
+ for (int i = 0; i < this.mInfoText.length; i++) {
+ StringBuilder sb = this.mInfoText[i];
+ sb.delete(0, sb.length());
+ if (i == 0) {
+ sb.append("th ");
+ pad(sb, 9, this.mThresholdInfo.start);
+ sb.append(" ");
+ pad(sb, 9, this.mThresholdInfo.distance);
+ sb.append(' ');
+ pad(sb, MAX_POINTERS, (int) this.mThresholdInfo.elapsed);
+ sb.append("ms");
+ } else if (i - 1 < dpc) {
+ this.mGestures.getDownPointerInfo(i - 1, this.mDownPointerInfo);
+ sb.append('p');
+ sb.append(this.mDownPointerInfo.downPointerId);
+ sb.append(' ');
+ sb.append('(');
+ pad(sb, 4, (int) this.mDownPointerInfo.downX);
+ sb.append(',');
+ pad(sb, 4, (int) this.mDownPointerInfo.downY);
+ sb.append(')');
+ sb.append(' ');
+ sb.append('(');
+ pad(sb, 4, (int) (this.mDownPointerInfo.currentX - this.mDownPointerInfo.downX));
+ sb.append(',');
+ pad(sb, 4, (int) (this.mDownPointerInfo.currentY - this.mDownPointerInfo.downY));
+ sb.append(')');
+ sb.append(' ');
+ pad(sb, 4, (int) this.mDownPointerInfo.elapsedThreshold);
+ sb.append("ms");
+ }
+ }
+ }
+
+ private static void pad(StringBuilder sb, int len, int num) {
+ int n = num;
+ if (num < 0) {
+ n = Math.abs(num);
+ len--;
+ }
+ int nl = n > 0 ? ((int) Math.log10((double) n)) + 1 : 1;
+ while (true) {
+ int nl2 = nl + 1;
+ if (nl >= len) {
+ break;
+ }
+ sb.append(' ');
+ nl = nl2;
+ }
+ if (num < 0) {
+ sb.append('-');
+ }
+ sb.append(n);
+ }
+
+ private static List<float[]> allocatePoints() {
+ List<float[]> points = new ArrayList();
+ for (int i = 0; i < MAX_POINTERS; i++) {
+ points.add(new float[MAX_POINTS]);
+ }
+ return points;
+ }
+
+ private void onMotionEvent(MotionEvent ev) {
+ long evt;
+ int p;
+ Log.d(TAG, "EVENT " + ev);
+ if (ev.getActionMasked() == 0) {
+ clearScribbles();
+ }
+ int hs = ev.getHistorySize();
+ int ps = ev.getPointerCount();
+ for (int h = 0; h < hs; h++) {
+ evt = ev.getHistoricalEventTime(h);
+ for (p = 0; p < ps; p++) {
+ int pid = ev.getPointerId(p);
+ float hx = ev.getHistoricalX(p, h);
+ float hy = ev.getHistoricalY(p, h);
+ String str = TAG;
+ Object[] objArr = new Object[MAX_POINTERS];
+ objArr[0] = Long.valueOf(evt);
+ objArr[1] = Integer.valueOf(p);
+ objArr[2] = Integer.valueOf(pid);
+ objArr[3] = Float.valueOf(hx);
+ objArr[4] = Float.valueOf(hy);
+ Log.d(str, String.format("h.evt=%s p=%s pid=%s x=%s y=%s", objArr));
+ recordScribblePoint(pid, hx, hy);
+ }
+ }
+ evt = ev.getEventTime();
+ for (p = 0; p < ps; p++) {
+ int pid = ev.getPointerId(p);
+ float x = ev.getX(p);
+ float y = ev.getY(p);
+ String str = TAG;
+ Object[] objArr = new Object[MAX_POINTERS];
+ objArr[0] = Long.valueOf(evt);
+ objArr[1] = Integer.valueOf(p);
+ objArr[2] = Integer.valueOf(pid);
+ objArr[3] = Float.valueOf(x);
+ objArr[4] = Float.valueOf(y);
+ Log.d(str, String.format("c.evt=%s p=%s pid=%s x=%s y=%s", objArr));
+ recordScribblePoint(pid, x, y);
+ }
+ invalidate();
+ }
+
+ private void clearScribbles() {
+ for (int i = 0; i < this.mScribbleCounts.length; i++) {
+ this.mScribbleCounts[i] = 0;
+ }
+ }
+
+ private void recordScribblePoint(int pid, float x, float y) {
+ if (pid < MAX_POINTERS) {
+ float[] pts = (float[]) this.mScribblePoints.get(pid);
+ int oldCount = this.mScribbleCounts[pid];
+ if (oldCount + 4 >= MAX_POINTS) {
+ return;
+ }
+ int[] iArr;
+ if (oldCount == 0) {
+ pts[oldCount + 0] = x;
+ pts[oldCount + 1] = y;
+ iArr = this.mScribbleCounts;
+ iArr[pid] = iArr[pid] + 2;
+ return;
+ }
+ pts[oldCount + 0] = x;
+ pts[oldCount + 1] = y;
+ pts[oldCount + 2] = x;
+ pts[oldCount + 3] = y;
+ iArr = this.mScribbleCounts;
+ iArr[pid] = iArr[pid] + 4;
+ }
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/TransparencyToggleButton.java b/KBars/app/src/main/java/js/kbars/TransparencyToggleButton.java
new file mode 100644
index 0000000..ef14d85
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/TransparencyToggleButton.java
@@ -0,0 +1,43 @@
+package js.kbars;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.Window;
+import android.widget.Button;
+
+public final class TransparencyToggleButton extends Button {
+ private final String mDescription;
+ private boolean mTransparent;
+ private final int mWmFlag;
+
+ public TransparencyToggleButton(Context context, String description, int wmFlag) {
+ super(context);
+ this.mDescription = description;
+ this.mWmFlag = wmFlag;
+ setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ TransparencyToggleButton.this.toggle("clicked");
+ }
+ });
+ update();
+ }
+
+ private void toggle(String reason) {
+ Log.d(KBarsActivity.TAG, "toggle reason=" + reason);
+ this.mTransparent = !this.mTransparent;
+ update();
+ }
+
+ private void update() {
+ setText("Make " + this.mDescription + " " + (this.mTransparent ? "opaque" : "transparent"));
+ Window w = ((Activity) getContext()).getWindow();
+ if (this.mTransparent) {
+ w.addFlags(this.mWmFlag);
+ } else {
+ w.clearFlags(this.mWmFlag);
+ }
+ }
+}
diff --git a/KBars/app/src/main/java/js/kbars/Util.java b/KBars/app/src/main/java/js/kbars/Util.java
new file mode 100644
index 0000000..5cd4971
--- /dev/null
+++ b/KBars/app/src/main/java/js/kbars/Util.java
@@ -0,0 +1,42 @@
+package js.kbars;
+
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+import java.lang.reflect.Field;
+
+public class Util {
+ public static String logTag(Class<?> c) {
+ return "kbars." + c.getSimpleName();
+ }
+
+ public static Object getField(Object obj, String fieldName) {
+ Class<?> c = obj.getClass();
+ try {
+ if (obj instanceof String) {
+ c = c.getClassLoader().loadClass((String) obj);
+ obj = null;
+ }
+ Field f = c.getDeclaredField(fieldName);
+ f.setAccessible(true);
+ return f.get(obj);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ public static int getDensityDpi(Context context) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ ((WindowManager) context.getSystemService("window")).getDefaultDisplay().getMetrics(metrics);
+ return metrics.densityDpi;
+ }
+
+ public static String getVersionName(Context context) {
+ try {
+ return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/KBars/app/src/main/res/drawable-hdpi-v4/ic_launcher.png b/KBars/app/src/main/res/drawable-hdpi-v4/ic_launcher.png
new file mode 100644
index 0000000..b899b9e
--- /dev/null
+++ b/KBars/app/src/main/res/drawable-hdpi-v4/ic_launcher.png
Binary files differ
diff --git a/KBars/app/src/main/res/drawable-mdpi-v4/ic_launcher.png b/KBars/app/src/main/res/drawable-mdpi-v4/ic_launcher.png
new file mode 100644
index 0000000..d2b481b
--- /dev/null
+++ b/KBars/app/src/main/res/drawable-mdpi-v4/ic_launcher.png
Binary files differ
diff --git a/KBars/app/src/main/res/drawable-xhdpi-v4/ic_launcher.png b/KBars/app/src/main/res/drawable-xhdpi-v4/ic_launcher.png
new file mode 100644
index 0000000..4a26260
--- /dev/null
+++ b/KBars/app/src/main/res/drawable-xhdpi-v4/ic_launcher.png
Binary files differ
diff --git a/KBars/app/src/main/res/drawable-xxhdpi-v4/ic_launcher.png b/KBars/app/src/main/res/drawable-xxhdpi-v4/ic_launcher.png
new file mode 100644
index 0000000..cdae26b
--- /dev/null
+++ b/KBars/app/src/main/res/drawable-xxhdpi-v4/ic_launcher.png
Binary files differ
diff --git a/KBars/app/src/main/res/raw/clipcanvas.mp4 b/KBars/app/src/main/res/raw/clipcanvas.mp4
new file mode 100644
index 0000000..e41f4c7
--- /dev/null
+++ b/KBars/app/src/main/res/raw/clipcanvas.mp4
Binary files differ
diff --git a/KBars/app/src/main/res/values/strings.xml b/KBars/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ea4b9c2
--- /dev/null
+++ b/KBars/app/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">(kbars)</string>
+ <string name="action_landscape">Landscape</string>
+ <string name="action_portrait">Portrait</string>
+ <string name="action_reverse_landscape">Reverse Landscape</string>
+ <string name="action_reverse_portrait">Reverse Portrait</string>
+</resources>
diff --git a/KBars/app/src/main/res/values/styles.xml b/KBars/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..fd4fc97
--- /dev/null
+++ b/KBars/app/src/main/res/values/styles.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="AppBaseTheme">
+ </style>
+ <style name="AppTheme">
+ </style>
+</resources>
diff --git a/KBars/build.gradle b/KBars/build.gradle
new file mode 100644
index 0000000..2e0233b
--- /dev/null
+++ b/KBars/build.gradle
@@ -0,0 +1,28 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ ext.kotlin_version = '1.1.2-4'
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.0.0-alpha4'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ mavenCentral()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/KBars/settings.gradle b/KBars/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/KBars/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/PermissionApp/Android.mk b/PermissionApp/Android.mk
index 00170ca..1111750 100644
--- a/PermissionApp/Android.mk
+++ b/PermissionApp/Android.mk
@@ -2,12 +2,11 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := PermissionApp
-#LOCAL_CERTIFICATE := platform
-
include $(BUILD_PACKAGE)
diff --git a/PermissionApp/AndroidManifest.xml b/PermissionApp/AndroidManifest.xml
index 8f35b34..c47fb6c 100644
--- a/PermissionApp/AndroidManifest.xml
+++ b/PermissionApp/AndroidManifest.xml
@@ -3,7 +3,6 @@
android:versionCode="1"
android:versionName="1.0">
- <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="22"/>
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
diff --git a/PermissionApp/src/foo/bar/permission/PermissionActivity.java b/PermissionApp/src/foo/bar/permission/PermissionActivity.java
index 529eccf..eb1d779 100644
--- a/PermissionApp/src/foo/bar/permission/PermissionActivity.java
+++ b/PermissionApp/src/foo/bar/permission/PermissionActivity.java
@@ -20,13 +20,9 @@ import android.Manifest;
import android.app.Activity;
import android.app.LoaderManager;
import android.bluetooth.BluetoothDevice;
-import android.companion.AssociationRequest;
-import android.companion.BluetoothDeviceFilter;
-import android.companion.BluetoothLEDeviceFilter;
import android.companion.CompanionDeviceManager;
import android.content.CursorLoader;
import android.content.Intent;
-import android.content.IntentSender;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.database.Cursor;
diff --git a/PrintApp/Android.mk b/PrintApp/Android.mk
index 81ab231..f09a7ea 100644
--- a/PrintApp/Android.mk
+++ b/PrintApp/Android.mk
@@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/PrintService/Android.mk b/PrintService/Android.mk
index 6d4ff7f..8581d35 100644
--- a/PrintService/Android.mk
+++ b/PrintService/Android.mk
@@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/SlicesApp/Android.mk b/SlicesApp/Android.mk
new file mode 100644
index 0000000..871aa4b
--- /dev/null
+++ b/SlicesApp/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := SlicesApp
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src)
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
diff --git a/SlicesApp/AndroidManifest.xml b/SlicesApp/AndroidManifest.xml
new file mode 100644
index 0000000..87def87
--- /dev/null
+++ b/SlicesApp/AndroidManifest.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.experimental.slicesapp">
+
+ <uses-sdk
+ android:minSdkVersion="26" />
+
+ <uses-permission android:name="android.permission.BIND_SLICE" />
+
+ <application android:label="@string/app_label">
+ <activity android:name=".SlicesActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW_SLICE" />
+ <data android:scheme="content"
+ android:host="com.android.experimental.slicesapp"
+ android:pathPrefix="/main" />
+ </intent-filter>
+
+ <meta-data android:name="android.metadata.SLICE_URI"
+ android:value="content://com.android.experimental.slicesapp/main" />
+ </activity>
+
+ <provider android:name=".SlicesProvider"
+ android:authorities="com.android.experimental.slicesapp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.EXAMPLE_SLICE_INTENT" />
+ </intent-filter>
+ </provider>
+
+ <receiver
+ android:name=".SlicesBroadcastReceiver"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.EXAMPLE_SLICE_ACTION"/>
+ </intent-filter>
+ </receiver>
+
+ </application>
+
+</manifest>
+
diff --git a/SlicesApp/res/drawable/ic_add.xml b/SlicesApp/res/drawable/ic_add.xml
new file mode 100644
index 0000000..56d3416
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_add.xml
@@ -0,0 +1,27 @@
+<!--
+Copyright (C) 2014 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 xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.0,13.0l-6.0,0.0l0.0,6.0l-2.0,0.0l0.0,-6.0L5.0,13.0l0.0,-2.0l6.0,0.0L11.0,5.0l2.0,0.0l0.0,6.0l6.0,0.0l0.0,2.0z"/>
+ <path
+ android:pathData="M0 0h24v24H0z"
+ android:fillColor="#00000000"/>
+</vector>
diff --git a/SlicesApp/res/drawable/ic_camera.xml b/SlicesApp/res/drawable/ic_camera.xml
new file mode 100644
index 0000000..1a2ef24
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_camera.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M 12 8.8 C 13.7673111995 8.8 15.2 10.2326888005 15.2 12 C 15.2 13.7673111995 13.7673111995 15.2 12 15.2 C 10.2326888005 15.2 8.8 13.7673111995 8.8 12 C 8.8 10.2326888005 10.2326888005 8.8 12 8.8 Z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1 .9 2 2 2h16c1.1 0 2-.9
+2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5
+5-2.24 5-5 5z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_car.xml b/SlicesApp/res/drawable/ic_car.xml
new file mode 100644
index 0000000..cdf0a6d
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_car.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21 .42 -1.42 1.01L3 12v8c0
+.55 .45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55 .45 1 1 1h1c.55 0 1-.45
+1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5 .67 1.5
+1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5 .67 1.5
+1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_create.xml b/SlicesApp/res/drawable/ic_create.xml
new file mode 100644
index 0000000..2ab2fb7
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_create.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+</vector>
diff --git a/SlicesApp/res/drawable/ic_done.xml b/SlicesApp/res/drawable/ic_done.xml
new file mode 100644
index 0000000..5dc4039
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_done.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_down.xml b/SlicesApp/res/drawable/ic_down.xml
new file mode 100644
index 0000000..1399b16
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_down.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
+ <path
+ android:pathData="M0-.75h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_home.xml b/SlicesApp/res/drawable/ic_home.xml
new file mode 100644
index 0000000..2332f3f
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_home.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_list.xml b/SlicesApp/res/drawable/ic_list.xml
new file mode 100644
index 0000000..6e8b482
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_list.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7
+7v2h14V7H7z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_next.xml b/SlicesApp/res/drawable/ic_next.xml
new file mode 100644
index 0000000..0b61487
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_next.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_pause.xml b/SlicesApp/res/drawable/ic_pause.xml
new file mode 100644
index 0000000..a581384
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_pause.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_photo.xml b/SlicesApp/res/drawable/ic_photo.xml
new file mode 100644
index 0000000..782c38c
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_photo.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1 .9 2 2 2h14c1.1 0 2-.9
+2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_play.xml b/SlicesApp/res/drawable/ic_play.xml
new file mode 100644
index 0000000..7457bac
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_play.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M8 5v14l11-7z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_prev.xml b/SlicesApp/res/drawable/ic_prev.xml
new file mode 100644
index 0000000..3bcca96
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_prev.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M6 6h2v12H6zm3.5 6l8.5 6V6z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_remove.xml b/SlicesApp/res/drawable/ic_remove.xml
new file mode 100644
index 0000000..76bd217
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_remove.xml
@@ -0,0 +1,27 @@
+<!--
+Copyright (C) 2014 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 xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.0,13.0L5.0,13.0l0.0,-2.0l14.0,0.0l0.0,2.0z"/>
+ <path
+ android:pathData="M0 0h24v24H0z"
+ android:fillColor="#00000000"/>
+</vector>
diff --git a/SlicesApp/res/drawable/ic_reply.xml b/SlicesApp/res/drawable/ic_reply.xml
new file mode 100644
index 0000000..af5643c
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_reply.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_share.xml b/SlicesApp/res/drawable/ic_share.xml
new file mode 100644
index 0000000..9942c06
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_share.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M18 16.08c-.76 0-1.44 .3 -1.96 .77 L8.91 12.7c.05-.23 .09 -.46 .09
+-.7s-.04-.47-.09-.7l7.05-4.11c.54 .5 1.25 .81 2.04 .81 1.66 0 3-1.34
+3-3s-1.34-3-3-3-3 1.34-3 3c0 .24 .04 .47 .09 .7L8.04 9.81C7.5 9.31 6.79 9 6
+9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05 .21 -.08
+.43 -.08 .65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31
+2.92-2.92s-1.31-2.92-2.92-2.92z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_slice.xml b/SlicesApp/res/drawable/ic_slice.xml
new file mode 100644
index 0000000..ce54529
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_slice.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M12,2C8.43,2 5.23,3.54 3.01,6L12,22l8.99,-16C18.78,3.55 15.57,2 12,2zM7,7c0,-1.1 0.9,-2 2,-2s2,0.9 2,2 -0.9,2 -2,2 -2,-0.9 -2,-2zM12,15c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/SlicesApp/res/drawable/ic_up.xml b/SlicesApp/res/drawable/ic_up.xml
new file mode 100644
index 0000000..dc67c88
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_up.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_video.xml b/SlicesApp/res/drawable/ic_video.xml
new file mode 100644
index 0000000..b19a75d
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_video.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55 .45 1 1 1h12c.55 0 1-.45
+1-1v-3.5l4 4v-11l-4 4z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_voice.xml b/SlicesApp/res/drawable/ic_voice.xml
new file mode 100644
index 0000000..e6c16f2
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_voice.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="#000000"
+ android:pathData="M7 24h2v-2H7v2zm5-11c1.66 0 2.99-1.34 2.99-3L15 4c0-1.66-1.34-3-3-3S9 2.34 9
+4v6c0 1.66 1.34 3 3 3zm-1 11h2v-2h-2v2zm4 0h2v-2h-2v2zm4-14h-1.7c0 3-2.54
+5.1-5.3 5.1S6.7 13 6.7 10H5c0 3.41 2.72 6.23 6 6.72V20h2v-3.28c3.28-.49 6-3.31
+6-6.72z" />
+ <path
+ android:pathData="M0 0h24v24H0z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/ic_work.xml b/SlicesApp/res/drawable/ic_work.xml
new file mode 100644
index 0000000..2017fd2
--- /dev/null
+++ b/SlicesApp/res/drawable/ic_work.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99 .89 -1.99 2L2
+19c0 1.11 .89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-6 0h-4V4h4v2z" />
+</vector> \ No newline at end of file
diff --git a/SlicesApp/res/drawable/mady.jpg b/SlicesApp/res/drawable/mady.jpg
new file mode 100644
index 0000000..8b61f1b
--- /dev/null
+++ b/SlicesApp/res/drawable/mady.jpg
Binary files differ
diff --git a/SlicesApp/res/drawable/weather_1.png b/SlicesApp/res/drawable/weather_1.png
new file mode 100644
index 0000000..7c4034e
--- /dev/null
+++ b/SlicesApp/res/drawable/weather_1.png
Binary files differ
diff --git a/SlicesApp/res/drawable/weather_2.png b/SlicesApp/res/drawable/weather_2.png
new file mode 100644
index 0000000..f1b6672
--- /dev/null
+++ b/SlicesApp/res/drawable/weather_2.png
Binary files differ
diff --git a/SlicesApp/res/drawable/weather_3.png b/SlicesApp/res/drawable/weather_3.png
new file mode 100644
index 0000000..a5db683
--- /dev/null
+++ b/SlicesApp/res/drawable/weather_3.png
Binary files differ
diff --git a/SlicesApp/res/drawable/weather_4.png b/SlicesApp/res/drawable/weather_4.png
new file mode 100644
index 0000000..0b7f3b0
--- /dev/null
+++ b/SlicesApp/res/drawable/weather_4.png
Binary files differ
diff --git a/SlicesApp/res/layout/activity_layout.xml b/SlicesApp/res/layout/activity_layout.xml
new file mode 100644
index 0000000..7ad0cfe
--- /dev/null
+++ b/SlicesApp/res/layout/activity_layout.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="center">
+
+ <FrameLayout
+ android:id="@+id/slice_preview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="16dp"
+ android:gravity="center">
+
+ <Button
+ android:id="@+id/subtract"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="-"/>
+
+ <TextView android:id="@+id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="48dp"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+
+ <Button
+ android:id="@+id/add"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="+"/>
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/auth"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Auth-everything"/>
+
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <CheckBox
+ android:id="@+id/header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Show header" />
+
+ <CheckBox
+ android:id="@+id/sub_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Show sub header" />
+
+ <CheckBox
+ android:id="@+id/show_action_row"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Show action row" />
+
+ </LinearLayout>
+</LinearLayout>
+
diff --git a/SlicesApp/res/values/strings.xml b/SlicesApp/res/values/strings.xml
new file mode 100644
index 0000000..ff364c5
--- /dev/null
+++ b/SlicesApp/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_label">Slices Client</string>
+</resources>
diff --git a/SlicesApp/src/com/android/experimental/slicesapp/SlicesActivity.java b/SlicesApp/src/com/android/experimental/slicesapp/SlicesActivity.java
new file mode 100644
index 0000000..9dc4049
--- /dev/null
+++ b/SlicesApp/src/com/android/experimental/slicesapp/SlicesActivity.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 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.experimental.slicesapp;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.Spinner;
+import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SlicesActivity extends Activity {
+
+ private static final String TAG = "SlicesActivity";
+
+ private int mState;
+ private ViewGroup mSlicePreviewFrame;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ SharedPreferences state = getSharedPreferences("slice", 0);
+ mState = 0;
+ if (state != null) {
+ mState = Integer.parseInt(state.getString(getUri().toString(), "0"));
+ }
+ setContentView(R.layout.activity_layout);
+ mSlicePreviewFrame = (ViewGroup) findViewById(R.id.slice_preview);
+ findViewById(R.id.subtract).setOnClickListener(v -> {
+ mState--;
+ state.edit().putString(getUri().toString(), String.valueOf(mState)).commit();
+ updateState();
+ getContentResolver().notifyChange(getUri(), null);
+ });
+ findViewById(R.id.add).setOnClickListener(v -> {
+ mState++;
+ state.edit().putString(getUri().toString(), String.valueOf(mState)).commit();
+ updateState();
+ getContentResolver().notifyChange(getUri(), null);
+ });
+ findViewById(R.id.auth).setOnClickListener(v -> {
+ List<ApplicationInfo> packages = getPackageManager().getInstalledApplications(0);
+ packages.forEach(info -> grantUriPermission(info.packageName, getUri(),
+ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
+ });
+
+ Spinner spinner = findViewById(R.id.spinner);
+ List<String> list = new ArrayList<>();
+ list.add("Default");
+ list.add("Single-line");
+ list.add("Single-line action");
+ list.add("Two-line");
+ list.add("Two-line action");
+ list.add("Weather");
+ list.add("Messaging");
+ list.add("Keep actions");
+ list.add("Maps multi");
+ list.add("Settings");
+ list.add("Settings content");
+ ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
+ android.R.layout.simple_spinner_item, list);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(adapter);
+ spinner.setSelection(list.indexOf(state.getString("slice_type", "Default")));
+ spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ state.edit().putString("slice_type", list.get(position)).commit();
+ getContentResolver().notifyChange(getUri(), null);
+ updateSlice(getUri());
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+
+ bindCheckbox(R.id.header, state, "show_header");
+ bindCheckbox(R.id.sub_header, state, "show_sub_header");
+ bindCheckbox(R.id.show_action_row, state, "show_action_row");
+
+ updateState();
+ }
+
+ private void bindCheckbox(int id, SharedPreferences state, String key) {
+ CheckBox checkbox = findViewById(id);
+ checkbox.setChecked(state.getBoolean(key, false));
+ checkbox.setOnCheckedChangeListener((v, isChecked) -> {
+ state.edit().putBoolean(key, isChecked).commit();
+ getContentResolver().notifyChange(getUri(), null);
+ updateSlice(getUri());
+ });
+ }
+
+ private void updateState() {
+ ((TextView) findViewById(R.id.summary)).setText(String.valueOf(mState));
+ }
+
+ public Uri getUri() {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(getPackageName())
+ .appendPath("main").build();
+ }
+
+ private void updateSlice(Uri uri) {
+ }
+}
diff --git a/SlicesApp/src/com/android/experimental/slicesapp/SlicesBroadcastReceiver.java b/SlicesApp/src/com/android/experimental/slicesapp/SlicesBroadcastReceiver.java
new file mode 100644
index 0000000..26adaa7
--- /dev/null
+++ b/SlicesApp/src/com/android/experimental/slicesapp/SlicesBroadcastReceiver.java
@@ -0,0 +1,29 @@
+package com.android.experimental.slicesapp;
+
+import android.app.slice.Slice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+
+public class SlicesBroadcastReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (SlicesProvider.SLICE_ACTION.equals(action)) {
+ // A slice was clicked and we've got an action to handle
+ Bundle bundle = intent.getExtras();
+ String message = "";
+ boolean newState = false;
+ if (bundle != null) {
+ message = bundle.getString(SlicesProvider.INTENT_ACTION_EXTRA);
+ newState = bundle.getBoolean(Slice.EXTRA_TOGGLE_STATE);
+ }
+ String text = message + " new state: " + newState;
+ Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
+ }
+ }
+}
diff --git a/SlicesApp/src/com/android/experimental/slicesapp/SlicesProvider.java b/SlicesApp/src/com/android/experimental/slicesapp/SlicesProvider.java
new file mode 100644
index 0000000..125bbb7
--- /dev/null
+++ b/SlicesApp/src/com/android/experimental/slicesapp/SlicesProvider.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2017 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.experimental.slicesapp;
+
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.app.slice.Slice;
+import android.app.slice.Slice.Builder;
+import android.app.slice.SliceProvider;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+import java.util.function.Consumer;
+
+
+public class SlicesProvider extends SliceProvider {
+
+ private static final String TAG = "SliceProvider";
+ public static final String SLICE_INTENT = "android.intent.action.EXAMPLE_SLICE_INTENT";
+ public static final String SLICE_ACTION = "android.intent.action.EXAMPLE_SLICE_ACTION";
+ public static final String INTENT_ACTION_EXTRA = "android.intent.slicesapp.INTENT_ACTION_EXTRA";
+
+ private final int NUM_LIST_ITEMS = 10;
+
+ private SharedPreferences mSharedPrefs;
+
+ @Override
+ public boolean onCreate() {
+ mSharedPrefs = getContext().getSharedPreferences("slice", 0);
+ return true;
+ }
+
+ private Uri getIntentUri() {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(getContext().getPackageName())
+ .appendPath("main").appendPath("intent")
+ .build();
+ }
+
+ //@Override
+ public Uri onMapIntentToUri(Intent intent) {
+ if (intent.getAction().equals(SLICE_INTENT)) {
+ return getIntentUri();
+ }
+ return null;//super.onMapIntentToUri(intent);
+ }
+
+ /**
+ * Overriding onBindSlice will generate one Slice for all modes.
+ * @param sliceUri
+ */
+ @Override
+ public Slice onBindSlice(Uri sliceUri) {
+ Log.w(TAG, "onBindSlice uri: " + sliceUri);
+ String type = mSharedPrefs.getString("slice_type", "Default");
+ if ("Default".equals(type)) {
+ return null;
+ }
+ Slice.Builder b = new Builder(sliceUri);
+ if (mSharedPrefs.getBoolean("show_header", false)) {
+ b.addText("Header", null, Slice.HINT_TITLE);
+ if (mSharedPrefs.getBoolean("show_sub_header", false)) {
+ b.addText("Sub-header", null);
+ }
+ }
+ if (sliceUri.equals(getIntentUri())) {
+ type = "Intent";
+ }
+ switch (type) {
+ case "Shortcut":
+ b.addColor(Color.CYAN, null).addIcon(
+ Icon.createWithResource(getContext(), R.drawable.mady),
+ null,
+ Slice.HINT_LARGE);
+ break;
+ case "Single-line":
+ b.addSubSlice(makeList(new Slice.Builder(b), this::makeSingleLine,
+ this::addIcon));
+ addPrimaryAction(b);
+ break;
+ case "Single-line action":
+ b.addSubSlice(makeList(new Slice.Builder(b), this::makeSingleLine,
+ this::addAltActions));
+ addPrimaryAction(b);
+ break;
+ case "Two-line":
+ b.addSubSlice(makeList(new Slice.Builder(b), this::makeTwoLine,
+ this::addIcon));
+ addPrimaryAction(b);
+ break;
+ case "Two-line action":
+ b.addSubSlice(makeList(new Slice.Builder(b), this::makeTwoLine,
+ this::addAltActions));
+ addPrimaryAction(b);
+ break;
+ case "Weather":
+ b.addSubSlice(createWeather(new Slice.Builder(b).addHints(Slice.HINT_HORIZONTAL)));
+ break;
+ case "Messaging":
+ createConversation(b);
+ break;
+ case "Keep actions":
+ b.addSubSlice(createKeepNote(new Slice.Builder(b)));
+ break;
+ case "Maps multi":
+ b.addSubSlice(createMapsMulti(new Slice.Builder(b)
+ .addHints(Slice.HINT_HORIZONTAL)));
+ break;
+ case "Intent":
+ b.addSubSlice(createIntentSlice(new Slice.Builder(b)
+ .addHints(Slice.HINT_HORIZONTAL)));
+ break;
+ case "Settings":
+ createSettingsSlice(b);
+ break;
+ case "Settings content":
+ createSettingsContentSlice(b);
+ break;
+ }
+ if (mSharedPrefs.getBoolean("show_action_row", false)) {
+ Intent intent = new Intent(getContext(), SlicesActivity.class);
+ PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), 0, intent, 0);
+ b.addSubSlice(new Slice.Builder(b).addHints(Slice.HINT_ACTIONS)
+ .addAction(pendingIntent, new Slice.Builder(b)
+ .addText("Action1", null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_add),
+ null)
+ .build())
+ .addAction(pendingIntent, new Slice.Builder(b)
+ .addText("Action2", null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_remove),
+ null)
+ .build())
+ .addAction(pendingIntent, new Slice.Builder(b)
+ .addText("Action3", null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_add),
+ null)
+ .build())
+ .build());
+ }
+ return b.build();
+ }
+
+ private Slice createWeather(Builder grid) {
+ grid.addSubSlice(new Slice.Builder(grid)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.weather_1),
+ Slice.HINT_LARGE)
+ .addText("MON", null)
+ .addText("69\u00B0", null, Slice.HINT_LARGE).build());
+ grid.addSubSlice(new Slice.Builder(grid)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.weather_2),
+ Slice.HINT_LARGE)
+ .addText("TUE", null)
+ .addText("71\u00B0", null, Slice.HINT_LARGE).build());
+ grid.addSubSlice(new Slice.Builder(grid)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.weather_3),
+ Slice.HINT_LARGE)
+ .addText("WED", null)
+ .addText("76\u00B0", null, Slice.HINT_LARGE).build());
+ grid.addSubSlice(new Slice.Builder(grid)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.weather_4),
+ Slice.HINT_LARGE)
+ .addText("THU", null)
+ .addText("69\u00B0", null, Slice.HINT_LARGE).build());
+ grid.addSubSlice(new Slice.Builder(grid)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.weather_2),
+ Slice.HINT_LARGE)
+ .addText("FRI", null)
+ .addText("71\u00B0", null, Slice.HINT_LARGE).build());
+ return grid.build();
+ }
+
+ private Slice createConversation(Builder b2) {
+ b2.addHints(Slice.HINT_LIST);
+ b2.addSubSlice(new Slice.Builder(b2)
+ .addText("yo home \uD83C\uDF55, I emailed you the info", null)
+ .addTimestamp(System.currentTimeMillis() - 20 * DateUtils.MINUTE_IN_MILLIS, null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.mady),
+ Slice.SUBTYPE_SOURCE,
+ Slice.HINT_TITLE, Slice.HINT_LARGE)
+ .build(), Slice.SUBTYPE_MESSAGE);
+ b2.addSubSlice(new Builder(b2)
+ .addText("just bought my tickets", null)
+ .addTimestamp(System.currentTimeMillis() - 10 * DateUtils.MINUTE_IN_MILLIS, null)
+ .build(), Slice.SUBTYPE_MESSAGE);
+ b2.addSubSlice(new Builder(b2)
+ .addText("yay! can't wait for getContext() weekend!\n"
+ + "\uD83D\uDE00", null)
+ .addTimestamp(System.currentTimeMillis() - 5 * DateUtils.MINUTE_IN_MILLIS, null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.mady),
+ Slice.SUBTYPE_SOURCE,
+ Slice.HINT_LARGE)
+ .build(), Slice.SUBTYPE_MESSAGE);
+ RemoteInput ri = new RemoteInput.Builder("someKey").setLabel("someLabel")
+ .setAllowFreeFormInput(true).build();
+ b2.addRemoteInput(ri, null);
+ return b2.build();
+ }
+
+ private Slice addIcon(Builder b) {
+ b.addIcon(Icon.createWithResource(getContext(), R.drawable.ic_add), null);
+ return b.build();
+ }
+
+ private void addAltActions(Builder builder) {
+ Intent intent = new Intent(getContext(), SlicesActivity.class);
+ PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), 0, intent, 0);
+ builder.addSubSlice(new Slice.Builder(builder).addHints(Slice.HINT_ACTIONS)
+ .addAction(pendingIntent, new Slice.Builder(builder)
+ .addText("Alt1", null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_add), null)
+ .build())
+ .addAction(pendingIntent, new Slice.Builder(builder)
+ .addText("Alt2", null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_remove), null)
+ .build())
+ .build());
+ }
+
+ private void makeSingleLine(Builder b) {
+ b.addText("Single-line list item text", null, Slice.HINT_TITLE);
+ }
+
+ private void makeTwoLine(Builder b) {
+ b.addText("Two-line list item text", null, Slice.HINT_TITLE);
+ b.addText("Secondary text", null);
+ }
+
+ private void addPrimaryAction(Builder b) {
+ Intent intent = new Intent(getContext(), SlicesActivity.class);
+ PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0);
+ b.addSubSlice(new Slice.Builder(b).addAction(pi,
+ new Slice.Builder(b).addColor(0xFFFF5722, null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_slice),
+ Slice.HINT_TITLE)
+ .addText("Slice App", null, Slice.HINT_TITLE)
+ .build()).addHints(Slice.HINT_HIDDEN, Slice.HINT_TITLE).build());
+ }
+
+ private Slice makeList(Builder list, Consumer<Builder> lineCreator,
+ Consumer<Builder> lineHandler) {
+ list.addHints(Slice.HINT_LIST);
+ for (int i = 0; i < NUM_LIST_ITEMS; i++) {
+ Builder b = new Builder(list);
+ lineCreator.accept(b);
+ lineHandler.accept(b);
+ list.addSubSlice(b.build());
+ }
+ return list.build();
+ }
+
+ private Slice createKeepNote(Builder b) {
+ Intent intent = new Intent(getContext(), SlicesActivity.class);
+ PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0);
+ RemoteInput ri = new RemoteInput.Builder("someKey").setLabel("someLabel")
+ .setAllowFreeFormInput(true).build();
+ return b.addText("Create new note", null, Slice.HINT_TITLE).addText("with keep", null)
+ .addColor(0xffffc107, null)
+ .addAction(pi, new Slice.Builder(b)
+ .addText("List", null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_list), null)
+ .build())
+ .addAction(pi, new Slice.Builder(b)
+ .addText("Voice note", null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_voice), null)
+ .build())
+ .addAction(pi, new Slice.Builder(b)
+ .addText("Camera", null)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_camera), null)
+ .build())
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_create), null)
+ .addRemoteInput(ri, null)
+ .build();
+ }
+
+ private Slice createMapsMulti(Builder b) {
+ Intent intent = new Intent(getContext(), SlicesActivity.class);
+ PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0);
+ b.addHints(Slice.HINT_HORIZONTAL, Slice.HINT_LIST);
+
+ b.addSubSlice(new Slice.Builder(b)
+ .addAction(pi, new Slice.Builder(b)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_home), null)
+ .build())
+ .addText("Home", null, Slice.HINT_LARGE)
+ .addText("25 min", null).build());
+ b.addSubSlice(new Slice.Builder(b)
+ .addAction(pi, new Slice.Builder(b)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_work), null)
+ .build())
+ .addText("Work", null, Slice.HINT_LARGE)
+ .addText("1 hour 23 min", null).build());
+ b.addSubSlice(new Slice.Builder(b)
+ .addAction(pi, new Slice.Builder(b)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_car), null)
+ .build())
+ .addText("Mom's", null, Slice.HINT_LARGE)
+ .addText("37 min", null).build());
+ b.addColor(0xff0B8043, null);
+ return b.build();
+ }
+
+ private Slice createIntentSlice(Builder b) {
+ Intent intent = new Intent(getContext(), SlicesActivity.class);
+
+ PendingIntent pi = PendingIntent.getActivity(getContext(), 0, intent, 0);
+
+ b.addHints(Slice.HINT_LIST).addColor(0xff0B8043, null);
+ b.addSubSlice(new Slice.Builder(b)
+ .addAction(pi, new Slice.Builder(b)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_next), null)
+ .build())
+ .addText("Next", null, Slice.HINT_LARGE).build());
+ b.addSubSlice(new Slice.Builder(b)
+ .addAction(pi, new Slice.Builder(b)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_play), null)
+ .build())
+ .addText("Play", null, Slice.HINT_LARGE).build());
+ b.addSubSlice(new Slice.Builder(b)
+ .addAction(pi, new Slice.Builder(b)
+ .addIcon(Icon.createWithResource(getContext(), R.drawable.ic_prev), null)
+ .build())
+ .addText("Previous", null, Slice.HINT_LARGE).build());
+ return b.build();
+ }
+
+ private Slice.Builder createSettingsSlice(Builder b) {
+ b.addSubSlice(new Slice.Builder(b)
+ .addAction(getIntent("toggled"), new Slice.Builder(b)
+ .addText("Wi-fi", null)
+ .addText("GoogleGuest", null)
+ .addHints(Slice.HINT_TOGGLE, Slice.HINT_SELECTED)
+ .build())
+ .build());
+ return b;
+ }
+
+ private Slice.Builder createSettingsContentSlice(Builder b) {
+ b.addSubSlice(new Slice.Builder(b)
+ .addAction(getIntent("main content"),
+ new Slice.Builder(b)
+ .addText("Wi-fi", null)
+ .addText("GoogleGuest", null)
+ .build())
+ .addAction(getIntent("toggled"),
+ new Slice.Builder(b)
+ .addHints(Slice.HINT_TOGGLE, Slice.HINT_SELECTED)
+ .build())
+ .build());
+ return b;
+ }
+
+ private PendingIntent getIntent(String message) {
+ Intent intent = new Intent(SLICE_ACTION);
+ intent.setClass(getContext(), SlicesBroadcastReceiver.class);
+ intent.putExtra(INTENT_ACTION_EXTRA, message);
+ PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ return pi;
+ }
+}
diff --git a/TestBack/src/foo/bar/testback/AccessibilityFocusManager.java b/TestBack/src/foo/bar/testback/AccessibilityFocusManager.java
new file mode 100644
index 0000000..c6b91ae
--- /dev/null
+++ b/TestBack/src/foo/bar/testback/AccessibilityFocusManager.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 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 foo.bar.testback;
+
+import static foo.bar.testback.AccessibilityNodeInfoUtils.findParent;
+
+import android.text.TextUtils;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Helper class to manage accessibility focus
+ */
+public class AccessibilityFocusManager {
+ private static final AccessibilityAction[] IGNORED_ACTIONS_FOR_A11Y_FOCUS = {
+ AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS,
+ AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
+ AccessibilityAction.ACTION_SELECT,
+ AccessibilityAction.ACTION_CLEAR_SELECTION,
+ AccessibilityAction.ACTION_SHOW_ON_SCREEN
+ };
+
+ public static final boolean canTakeAccessibilityFocus(AccessibilityNodeInfo nodeInfo) {
+ if (nodeInfo.isFocusable() || nodeInfo.isScreenReaderFocusable()) {
+ return true;
+ }
+
+ List<AccessibilityAction> actions = new ArrayList<>(nodeInfo.getActionList());
+ actions.removeAll(Arrays.asList(IGNORED_ACTIONS_FOR_A11Y_FOCUS));
+
+ // Nodes with relevant actions are always focusable
+ if (!actions.isEmpty()) {
+ return true;
+ }
+
+ // If a parent is specifically marked focusable, then this node should not be.
+ if (findParent(nodeInfo,
+ (parent) -> parent.isFocusable() || parent.isScreenReaderFocusable()) != null) {
+ return false;
+ }
+
+ return !TextUtils.isEmpty(nodeInfo.getText())
+ || !TextUtils.isEmpty(nodeInfo.getContentDescription());
+ };
+}
diff --git a/TestBack/src/foo/bar/testback/AccessibilityNodeInfoUtils.java b/TestBack/src/foo/bar/testback/AccessibilityNodeInfoUtils.java
new file mode 100644
index 0000000..f4e9398
--- /dev/null
+++ b/TestBack/src/foo/bar/testback/AccessibilityNodeInfoUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 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 foo.bar.testback;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.util.function.Predicate;
+
+/**
+ * Utility class for working with AccessibilityNodeInfo
+ */
+public class AccessibilityNodeInfoUtils {
+ public static AccessibilityNodeInfo findParent(
+ AccessibilityNodeInfo start, Predicate<AccessibilityNodeInfo> condition) {
+ AccessibilityNodeInfo parent = start.getParent();
+ if ((parent == null) || (condition.test(parent))) {
+ return parent;
+ }
+
+ return findParent(parent, condition);
+ }
+
+ public static AccessibilityNodeInfo findChildDfs(
+ AccessibilityNodeInfo start, Predicate<AccessibilityNodeInfo> condition) {
+ int numChildren = start.getChildCount();
+ for (int i = 0; i < numChildren; i++) {
+ AccessibilityNodeInfo child = start.getChild(i);
+ if (child != null) {
+ if (condition.test(child)) {
+ return child;
+ }
+ AccessibilityNodeInfo childResult = findChildDfs(child, condition);
+ if (childResult != null) {
+ return childResult;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/TestBack/src/foo/bar/testback/TestBackService.java b/TestBack/src/foo/bar/testback/TestBackService.java
index 53db125..bd5329b 100644
--- a/TestBack/src/foo/bar/testback/TestBackService.java
+++ b/TestBack/src/foo/bar/testback/TestBackService.java
@@ -1,8 +1,30 @@
+/*
+ * Copyright 2017 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 foo.bar.testback;
+import static foo.bar.testback.AccessibilityNodeInfoUtils.findChildDfs;
+import static foo.bar.testback.AccessibilityNodeInfoUtils.findParent;
+
+import static foo.bar.testback.AccessibilityFocusManager.CAN_TAKE_A11Y_FOCUS;
+
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
+import android.graphics.Rect;
import android.util.ArraySet;
import android.util.Log;
import android.view.WindowManager;
@@ -13,6 +35,7 @@ import android.widget.Button;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
public class TestBackService extends AccessibilityService {
@@ -20,6 +43,11 @@ public class TestBackService extends AccessibilityService {
private Button mButton;
+ int mRowIndexOfA11yFocus = -1;
+ int mColIndexOfA11yFocus = -1;
+ AccessibilityNodeInfo mCollectionWithAccessibiltyFocus;
+ AccessibilityNodeInfo mCurrentA11yFocus;
+
@Override
public void onCreate() {
super.onCreate();
@@ -29,53 +57,61 @@ public class TestBackService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
-// if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
-// Log.i(LOG_TAG, event.getText().toString());
-// //dumpWindows();
-// }
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER) {
-// Log.i(LOG_TAG, "Click event.isChecked()=" + event.isChecked()
-// + ((event.getSource() != null) ? " node.isChecked()="
-// + event.getSource().isChecked() : " node=null"));
-
- AccessibilityNodeInfo source = event.getSource();
- dumpWindow(source);
-// AccessibilityNodeInfo node = event.getSource();
-// if (node != null) {
-// node.refresh();
-// Log.i(LOG_TAG, "Clicked: " + node.getClassName() + " clicked:" + node.isChecked());
-// }
+ int eventType = event.getEventType();
+ AccessibilityNodeInfo source = event.getSource();
+ if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER) {
+ if (source != null) {
+ AccessibilityNodeInfo focusNode =
+ (CAN_TAKE_A11Y_FOCUS.test(source)) ? source : findParent(
+ source, AccessibilityFocusManager::canTakeAccessibilityFocus);
+ if (focusNode != null) {
+ focusNode.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ }
+ }
+ return;
}
- }
-
- @Override
- protected boolean onGesture(int gestureId) {
- switch (gestureId) {
- case AccessibilityService.GESTURE_SWIPE_DOWN: {
- showAccessibilityOverlay();
- } break;
- case AccessibilityService.GESTURE_SWIPE_UP: {
- hideAccessibilityOverlay();
- } break;
+ if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
+ mCurrentA11yFocus = source;
+ AccessibilityNodeInfo.CollectionItemInfo itemInfo = source.getCollectionItemInfo();
+ if (itemInfo == null) {
+ mRowIndexOfA11yFocus = -1;
+ mColIndexOfA11yFocus = -1;
+ mCollectionWithAccessibiltyFocus = null;
+ } else {
+ mRowIndexOfA11yFocus = itemInfo.getRowIndex();
+ mColIndexOfA11yFocus = itemInfo.getColumnIndex();
+ mCollectionWithAccessibiltyFocus = findParent(source,
+ (nodeInfo) -> nodeInfo.getCollectionInfo() != null);
+ }
+ Rect bounds = new Rect();
+ source.getBoundsInScreen(bounds);
}
- return super.onGesture(gestureId);
- }
- private void showAccessibilityOverlay() {
- WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
- params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+ if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED) {
+ mCurrentA11yFocus = null;
+ return;
+ }
- WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
- windowManager.addView(mButton, params);
- }
+ if (eventType == AccessibilityEvent.TYPE_WINDOWS_CHANGED) {
+ mRowIndexOfA11yFocus = -1;
+ mColIndexOfA11yFocus = -1;
+ mCollectionWithAccessibiltyFocus = null;
+ }
- private void hideAccessibilityOverlay() {
- WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
- windowManager.removeView(mButton);
+ if ((mCurrentA11yFocus == null) && (mCollectionWithAccessibiltyFocus != null)) {
+ // Look for a node to re-focus
+ AccessibilityNodeInfo focusRestoreTarget = findChildDfs(
+ mCollectionWithAccessibiltyFocus, (nodeInfo) -> {
+ AccessibilityNodeInfo.CollectionItemInfo collectionItemInfo =
+ nodeInfo.getCollectionItemInfo();
+ return (collectionItemInfo != null)
+ && (collectionItemInfo.getRowIndex() == mRowIndexOfA11yFocus)
+ && (collectionItemInfo.getColumnIndex() == mColIndexOfA11yFocus);
+ });
+ if (focusRestoreTarget != null) {
+ focusRestoreTarget.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ }
+ }
}
private void dumpWindows() {