diff options
author | Xin Li <delphij@google.com> | 2018-08-07 16:51:25 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-08-07 16:51:25 +0000 |
commit | c6c454c3b017244644e3ddba3c2975a386605359 (patch) | |
tree | a0d80646918ef7aa73fd720b9e34828c755d9a3a | |
parent | 8f2b472ae1b1914335e3834242c33c26d1d76cad (diff) | |
parent | cc1b9f3e19e2decc5fd6be694698ed40e86a5e80 (diff) | |
download | experimental-oreo-mr1-1.2-iot-release.tar.gz |
Merge "Merge Android Pie into master"android-o-mr1-iot-release-smart-display-r3android-o-mr1-iot-release-1.0.5android-o-mr1-iot-release-1.0.4android-o-mr1-iot-release-1.0.3oreo-mr1-1.2-iot-releasemaster-cuttlefish-testing-release
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 Binary files differnew file mode 100644 index 0000000..aae8a33 --- /dev/null +++ b/KBars/app/src/main/assets/background.png 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 Binary files differnew file mode 100644 index 0000000..b899b9e --- /dev/null +++ b/KBars/app/src/main/res/drawable-hdpi-v4/ic_launcher.png 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 Binary files differnew file mode 100644 index 0000000..d2b481b --- /dev/null +++ b/KBars/app/src/main/res/drawable-mdpi-v4/ic_launcher.png 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 Binary files differnew file mode 100644 index 0000000..4a26260 --- /dev/null +++ b/KBars/app/src/main/res/drawable-xhdpi-v4/ic_launcher.png 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 Binary files differnew file mode 100644 index 0000000..cdae26b --- /dev/null +++ b/KBars/app/src/main/res/drawable-xxhdpi-v4/ic_launcher.png diff --git a/KBars/app/src/main/res/raw/clipcanvas.mp4 b/KBars/app/src/main/res/raw/clipcanvas.mp4 Binary files differnew file mode 100644 index 0000000..e41f4c7 --- /dev/null +++ b/KBars/app/src/main/res/raw/clipcanvas.mp4 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 Binary files differnew file mode 100644 index 0000000..8b61f1b --- /dev/null +++ b/SlicesApp/res/drawable/mady.jpg diff --git a/SlicesApp/res/drawable/weather_1.png b/SlicesApp/res/drawable/weather_1.png Binary files differnew file mode 100644 index 0000000..7c4034e --- /dev/null +++ b/SlicesApp/res/drawable/weather_1.png diff --git a/SlicesApp/res/drawable/weather_2.png b/SlicesApp/res/drawable/weather_2.png Binary files differnew file mode 100644 index 0000000..f1b6672 --- /dev/null +++ b/SlicesApp/res/drawable/weather_2.png diff --git a/SlicesApp/res/drawable/weather_3.png b/SlicesApp/res/drawable/weather_3.png Binary files differnew file mode 100644 index 0000000..a5db683 --- /dev/null +++ b/SlicesApp/res/drawable/weather_3.png diff --git a/SlicesApp/res/drawable/weather_4.png b/SlicesApp/res/drawable/weather_4.png Binary files differnew file mode 100644 index 0000000..0b7f3b0 --- /dev/null +++ b/SlicesApp/res/drawable/weather_4.png 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() { |