First draft of Snippet sample project

This commit is contained in:
Vishal Ratna 2021-09-08 23:34:34 +05:30
Родитель 906fe5a028
Коммит 0840b7019f
58 изменённых файлов: 2674 добавлений и 0 удалений

19
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,19 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/misc.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
/app/build
/.idea/

91
app/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,91 @@
/build
/app/build
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
.idea/jarRepositories.xml
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof

40
app/build.gradle Normal file
Просмотреть файл

@ -0,0 +1,40 @@
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 31
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.microsoft.sample"
minSdkVersion 16
targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation project(':snippet')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

21
app/proguard-rules.pro поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Просмотреть файл

@ -0,0 +1,26 @@
package com.microsoft.sample;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.microsoft.sample", appContext.getPackageName());
}
}

Просмотреть файл

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.sample">
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name=".SampleApplication"
android:theme="@style/Theme.Sample">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Просмотреть файл

@ -0,0 +1,17 @@
package com.microsoft.sample;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.microsoft.snippet.Snippet;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Snippet.find("app_start").endCapture();
}
}

Просмотреть файл

@ -0,0 +1,18 @@
package com.microsoft.sample;
import android.app.Application;
import com.microsoft.snippet.Snippet;
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if(BuildConfig.DEBUG) {
Snippet.install(new Snippet.MeasuredExecutionPath());
Snippet.newFilter("SampleFilter");
Snippet.addFlag(Snippet.FLAG_METADATA_LINE | Snippet.FLAG_METADATA_THREAD_INFO);
}
Snippet.startCapture("app_start");
}
}

Просмотреть файл

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Просмотреть файл

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

Просмотреть файл

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Просмотреть файл

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Просмотреть файл

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Двоичные данные
app/src/main/res/mipmap-hdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.5 KiB

Двоичные данные
app/src/main/res/mipmap-hdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.2 KiB

Двоичные данные
app/src/main/res/mipmap-mdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.6 KiB

Двоичные данные
app/src/main/res/mipmap-mdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.3 KiB

Двоичные данные
app/src/main/res/mipmap-xhdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.8 KiB

Двоичные данные
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.3 KiB

Двоичные данные
app/src/main/res/mipmap-xxhdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.7 KiB

Двоичные данные
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 10 KiB

Двоичные данные
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Просмотреть файл

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Sample" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

Просмотреть файл

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

Просмотреть файл

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Sample</string>
</resources>

Просмотреть файл

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Sample" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

Просмотреть файл

@ -0,0 +1,17 @@
package com.microsoft.sample;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

25
build.gradle Normal file
Просмотреть файл

@ -0,0 +1,25 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

17
gradle.properties Normal file
Просмотреть файл

@ -0,0 +1,17 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true

Двоичные данные
gradle/wrapper/gradle-wrapper.jar поставляемый Normal file

Двоичный файл не отображается.

6
gradle/wrapper/gradle-wrapper.properties поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
#Wed Sep 08 23:02:35 IST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

172
gradlew поставляемый Executable file
Просмотреть файл

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat поставляемый Normal file
Просмотреть файл

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

3
settings.gradle Normal file
Просмотреть файл

@ -0,0 +1,3 @@
rootProject.name = "Sample"
include ':app'
include ':snippet'

1
snippet/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
/build

30
snippet/build.gradle Normal file
Просмотреть файл

@ -0,0 +1,30 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 31
buildToolsVersion "30.0.3"
defaultConfig {
minSdkVersion 16
targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree (dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.3.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

Просмотреть файл

21
snippet/proguard-rules.pro поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Просмотреть файл

@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.microsoft.snippet">
</manifest>

Просмотреть файл

@ -0,0 +1,53 @@
package com.microsoft.snippet;
/**
* Wraps all the information that can be returned through the library.
* If any other information is needed it can be added ad-hoc.
*/
public class ExecutionContext {
private String mClass;
private String mMethod;
private int mLineNo;
private String mThreadName;
private long mExecutionDuration;
void setClassName(String clazz) {
this.mClass = clazz;
}
void setMethod(String method) {
this.mMethod = method;
}
void setLineNo(int line) {
this.mLineNo = line;
}
void setThreadName(String threadName) {
this.mThreadName = threadName;
}
void setExecutionDuration(long duration) {
this.mExecutionDuration = duration;
}
public String getClassName() {
return this.mClass;
}
public String getMethodName() {
return this.mMethod;
}
public String getThreadName() {
return this.mThreadName;
}
public int getLineNo() {
return this.mLineNo;
}
public long getExecutionDuration() {
return this.mExecutionDuration;
}
}

Просмотреть файл

@ -0,0 +1,68 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
import androidx.annotation.NonNull;
import com.microsoft.snippet.token.ILogToken;
/**
* Execution path determines how core the functionality of this library should behave.
* It might be possible that we do not want to execute the code entirely in release builds or
* may want to add some extra information into the existing information and add it to files.
* We can plugin a custom execution path or method through this.
*/
public interface ExecutionPath {
/**
* Return a pair of delta: the time taken to execute the closure. And the standard log message
* printed by the Snippet.
*
* @param message Custom message to print on the log if any.
* @param closure Closure who execution duration needs to be measured.
* @return Pair of Delta and Log String
*/
@NonNull
ExecutionContext capture(String message, Snippet.Closure closure);
/**
* Return a pair of delta: the time taken to execute the closure. And the standard log message
* printed by the Snippet.
*
* @param closure Closure who execution duration needs to be measured.
* @return Pair of Delta and Log String
*/
@NonNull
ExecutionContext capture(Snippet.Closure closure);
/**
* Returns a log token and starts the measurement at this point. The token returned has a
* method endCapture() which ends the measurement.
*
* @return IToken
*/
ILogToken startCapture();
/**
* Gets a log token and ties it with a tag that can be used to search the instance of log token
* using {@link Snippet#find(String)}
*
* @param tag Unique tag for the log token. If 2 token try to use same tag, the first one to get
* the tag wins and other calls becomes no-op.
* @return IToken
*/
ILogToken startCapture(String tag);
/**
* Finds a log token with a tag which has been created previously. Using
* {@link Snippet#startCapture(String)}
*
* @param tag Custom tag. This tag will be used to search the log token across the app.
* @return IToken if any available attached with this tag
*/
ILogToken find(String tag);
}

Просмотреть файл

@ -0,0 +1,29 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
/**
* Provides a class that can be used for capturing variables in an anonymous class implementation.
*
* @param <T>
*/
public final class Final<T> {
private T mValue;
public Final() {
}
public Final(T value) {
this.mValue = value;
}
public T get() {
return mValue;
}
public void set(T value) {
this.mValue = value;
}
}

Просмотреть файл

@ -0,0 +1,21 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
import com.microsoft.snippet.token.ILogToken;
/**
* Log token Searcher. Not for external use
*/
interface ILogTokenSearcher {
/**
* Return the token for the tag provided else return null
*
* @param tag tag for the log token
* @return Log Token
*/
ILogToken search(String tag);
}

Просмотреть файл

@ -0,0 +1,119 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
import android.util.Log;
import androidx.annotation.NonNull;
import com.microsoft.snippet.token.ILogToken;
import com.microsoft.snippet.token.LogTokenState;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
/**
* LogToken Pool is helps recycling the log token objects that are used by Snippet.
*/
public final class LogTokenPool {
private static final String TAG = LogTokenPool.class.getSimpleName();
private final List<ILogToken> mPool = new LinkedList<>();
// A register to keep track of allocated tokens.
private final HashSet<Integer> mRegister = new HashSet<>();
LogTokenPool() {
}
@NonNull
ILogToken obtain() {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "obtain() called");
Log.d(TAG, "Number of objects in POOL while entering into obtain(): " + mPool.size());
}
ILogToken temp;
synchronized (this) {
if (mPool.size() > 0) {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "Pool has reusable objects available. Will use one.");
}
temp = mPool.remove(0);
boolean isAddingToRegisterSuccess = mRegister.add(temp.hashCode());
if (isAddingToRegisterSuccess) {
temp.setState(LogTokenState.ACTIVE);
return temp;
} else {
Log.d(TAG, "Register already has the hashcode belonging to the current token, we need to recycle it and create a new one! ");
return assureTokenWithUniqueHashCode(temp);
}
} else {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "Pool is empty, a new LogToken object will be created.");
}
temp = createTokenLocked();
boolean isAddingToRegisterSuccess = mRegister.add(temp.hashCode());
if (isAddingToRegisterSuccess) {
temp.setState(LogTokenState.ACTIVE);
return temp;
} else {
Log.d(TAG, "Register already has the hashcode belonging to the current token, we need to recycle it and create a new one! ");
return assureTokenWithUniqueHashCode(temp);
}
}
}
}
/**
* This method gives a one level safety against the situation where the hash codes of 2 log tokens
* become same accidentally given the hash functions are not messed up. It recycles the old token
* that has the same hash code that exists in the register and tries to generate a new token that
* will be having different hash code.
* If someone has messed the hash function badly. This will not help.
*
* @param oldToken old token that needs to get recycled.
* @return new token
*/
@NonNull
ILogToken assureTokenWithUniqueHashCode(ILogToken oldToken) {
// Return old token to the pool
oldToken.reset();
mPool.add(oldToken);
// create a new token and return it.
ILogToken newToken = createTokenLocked();
newToken.setState(LogTokenState.ACTIVE);
return newToken;
}
synchronized void recycle(@NonNull ILogToken token) {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "recycle() called");
Log.d(TAG, "Number of objects in POOL while entering into recycle(): " + mPool.size());
}
// See if we are returning token which was not created using obtain()
if (!mRegister.contains(token.hashCode())) {
throw new IllegalStateException("Trying to return object which was not created using obtain() "
+ "OR May be endCapture() was called multiple times on the same token object.");
} else {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, " Recycling the LogToken object in the pool.");
}
token.reset();
token.setState(LogTokenState.IN_POOL);
mPool.add(token);
mRegister.remove(token.hashCode());
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "Number of objects in POOL while after recycle(): " + mPool.size());
}
}
}
private synchronized ILogToken createTokenLocked() {
return new Snippet.LogToken();
}
}

Просмотреть файл

@ -0,0 +1,54 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
import android.util.Log;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* A thread safe implementation which let's the caller thread to set the value of the wrapping
* type once using {@link OneShot#set(Object)}, once the value is set, any other calls to set
* is not honoured. The value remains the same for the lifetime of the instance.
* Call {@link OneShot#get()} to access the wrapped instance.
*
* @param <T>
* @author vishalratna
*/
public final class OneShot<T> {
private static final String TAG = OneShot.class.getSimpleName();
private final AtomicReference<T> mData;
private final AtomicInteger mCounter;
public OneShot(T data) {
this.mData = new AtomicReference<>(data);
this.mCounter = new AtomicInteger(0);
}
public void set(T data) {
if (mCounter.compareAndSet(0, 1)) {
T oldVal = mData.get();
if (mData.compareAndSet(oldVal, data)) {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "OneShot value set successfully.");
}
} else {
if (Snippet.mPrintDebugLogs) {
Log.e(TAG, "OneShot already set after the counter was set to 1. Cannot change the value again.");
}
}
} else {
if (Snippet.mPrintDebugLogs) {
Log.e(TAG, "OneShot already set once. Cannot change the value again.");
}
}
}
public T get() {
return mData.get();
}
}

Просмотреть файл

@ -0,0 +1,28 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
/**
* Represents a Pair of objects
* @param <A> A
* @param <B> B
*/
public final class Pair<A, B> {
A a;
B b;
public Pair(A first, B second) {
this.a = first;
this.b = second;
}
public A getFirst() {
return a;
}
public B getSecond() {
return b;
}
}

Просмотреть файл

@ -0,0 +1,45 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
import androidx.annotation.NonNull;
import com.microsoft.snippet.token.ILogToken;
/**
* This is the NOOP execution path that is used by Release build types.
*/
public class ReleaseExecutionPath implements ExecutionPath {
private static final ExecutionContext RESULT = new ExecutionContext();
@Override
@NonNull
public ExecutionContext capture(String message, Snippet.Closure closure) {
closure.invoke();
return RESULT;
}
@Override
@NonNull
public ExecutionContext capture(Snippet.Closure closure) {
closure.invoke();
return RESULT;
}
@Override
public ILogToken startCapture() {
return Snippet.NO_OP_TOKEN;
}
@Override
public ILogToken startCapture(String tag) {
return Snippet.NO_OP_TOKEN;
}
@Override
public ILogToken find(String tag) {
return Snippet.NO_OP_TOKEN;
}
}

Просмотреть файл

@ -0,0 +1,791 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.microsoft.snippet.token.AttenuatedLogToken;
import com.microsoft.snippet.token.ILogToken;
import com.microsoft.snippet.token.LogTokenState;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Snippet is a small library which can be used for measuring the time taken to execute a code
* snippet. This library intends to reduce the boiler place code needed to add the start and end timestamps,
* adding the execution context such as class,method and line number or any other relevant data and
* printing the logs. The work is tedious and it becomes a nightmare when we have to do it across the
* code base. The API provided by this library could be used to monitor PRs in the PR reviews where
* the reviewer could ask for numbers for a specific snippet and compare the before and after numbers.
* <h2> There are 2 ways to measure time in Snippet</h2>
* <ol>
* <li> Using <code>capture</code> APIs of Snippet</li>
* <li> Using log tokens and <code>startCapture</code> and <code>endCapture</code> APIs </li>
* </ol>
* <p>
* Example usage with <code>Snippet.capture</code>:
* <pre>
* {@code
* Snippet.capture(() -> {
* final ActionBar actionBar = getSupportActionBar();
* if (actionBar != null && actionBar.isShowing()) {
* if (mToolbar != null) {
* ViewCompat.setElevation((View) mToolbar.getParent(), 0F);
* mToolbar.animate()
* .translationY(-mToolbar.getHeight())
* .setInterpolator(new AccelerateDecelerateInterpolator())
* .withEndAction(new Runnable() {
* {@literal}Override
* public void run() {
* actionBar.hide();
* if (mToolbarTranslateYListener != null) {
* mToolbarTranslateYListener.onAnimationFinished();
* }
* }
* })
* .start();
* } else {
* actionBar.hide();
* }
* }
* });
* }
* </pre>
* Here library is going to measure time used to execute the code represented by the underlying lambda.
* <p>
* When you intent to measure the execution times for code which is not spread across multiple
* methods and classes, capture APIs can be used. There is one caveat though, while capturing, we pass
* a lambda for the closure representing the code snippet, this might be a problem if we want to capture
* some non final variables outside the scope of lambda(Java does not support that). For that use, {@link Final} to create a
* wrapper around your variable and use {@link Final#get()} and {@link Final#set(Object)}()} methods.
* <p>
* Another approach is through {@link Snippet#startCapture()} & {@link LogToken#endCapture()} APIs.
* <code>startCapture</code> is going to return a token representing your execution it can be passed
* from one component to another. The moment you call <code>endCapture()</code> it will measure the
* time and print the logs.
* <p>
* Example usage of <code>Snippet.startCapture()</code> and <code>LogToken.endCapture()</code>:
* <pre>
* {@code
* Snippet.LogToken token = Snippet.startCapture(); // start the measurement
* getEditText().setOnTouchListener((v, event) -> {
* mIsQueryTextFromVoiceInput = false;
* if (!TextUtils.isEmpty(getEditText().getText())) {
* updateClearIconAndVoiceEntryVisibility(VISIBLE, GONE);
* }
*
* if (mOnEditTextTouchListener != null) {
* return mOnEditTextTouchListener.onTouch(v, event);
* }
*
* return false;
* });
*
* initVoiceInputEntry(context);
* token.endCapture(); // end measurement and print logs.
* }
* </pre>
* We can use <code>startCapture()</code> method with a TAG also. This is particularly useful when
* the execution is spread across multiple classes. We can use, {@link Snippet#find(String)} to find
* the log token that was created with this tag. The logtoken received can be then used normally.
* {@link LogToken} objects are internally obtained through a pool so, Snippet tries it best to
* recycle the objects again and again.
* <p>
* There is concept of filter, which can be used to filter the logcat output. You can set the filter
* using {@link Snippet#newFilter(String)}. Default filter is <b>Snippet</b>
* While this is global filter for all the logs, you can still choose to have a different filter for a
* particular LogToken using {@link LogToken#overrideFilter(String)} which will override the global filter.
* <p>
* Snippet can print Class, Method and Line number information in the logs as a part of execution
* context. By default it prints class and method name. You can choose to have your combination of
* details through {@link Snippet#addFlag(int)}
* <b>Valid options are:</b>
* <ol>
*
* <li>{@link Snippet#FLAG_METADATA_CLASS}</li>
*
* <li>{@link Snippet#FLAG_METADATA_METHOD}</li>
*
* <li>{@link Snippet#FLAG_METADATA_LINE}</li>
*
* <li>{@link Snippet#FLAG_NONE}</li>
* </ol>
* <p>
* While these information should be enough but if you need to add some more information to the logs
* like variable values, states etc then use {@link Snippet#capture(String, Closure)} and
* {@link LogToken#endCapture(String)} overloads for adding custom messages to the logcat
* output.
* <p>
* Snippet also supports the concept of <b>ThreadLocks</b>. ThreadLocks is a feature where a log token
* can declare the thread creating the log token would only be able to call endCapture(). If other
* thread tries to call endCapture(). It will log an error. To enable thread locks one should
* call {@link LogToken#enableThreadLock()} and to check if the ThreadLock is enabled or not
* {@link LogToken#isThreadLockEnabled()} can be used.
* </p>
*
* <p>
* There are times while you are measuring a sequence of code and you would like to measure how
* much time some steps take within that sequence. As an example, while you are measuring some
* method that takes 300ms to execute, it could have multiple areas inside it that could be adding up
* to that number. So, to measure that {@link LogToken#addSplit()} and {@link LogToken#addSplit(String)}
* could be used. Each call to add split print the time takes since the last addSplit() was called.
* If addSplit() is called for the first time, then it would measure the time from startCapture().
* </p>
*
* @author vishalratna
*/
public final class Snippet {
private static final String TAG = LogToken.class.getSimpleName();
static final AttenuatedLogToken NO_OP_TOKEN = new AttenuatedLogToken();
private static final ExecutionContext EMPTY_CONTEXT = new ExecutionContext();
public static final int FLAG_METADATA_CLASS = 1 << 31;
public static final int FLAG_METADATA_METHOD = 1 << 30;
public static final int FLAG_METADATA_LINE = 1 << 29;
public static final int FLAG_METADATA_THREAD_INFO = 1 << 28;
public static final int FLAG_NONE = 0;
private static String primaryFilter = Snippet.class.getSimpleName();
private static final LogTokenPool OBJECT_POOL;
private static String packageNameFilter = "com.microsoft";
private static StackAnalyser stackAnalyser = new StackAnalyser(packageNameFilter);
private static final TagHelper TAG_HELPER;
private static int mFlags = FLAG_METADATA_CLASS | FLAG_METADATA_METHOD;
private static final String SEPARATOR = "|::::|";
private static final OneShot<Boolean> SHOULD_PRINT_DEBUG_LOGS = new OneShot<>(false);
private static final OneShot<ExecutionPath> EXECUTION_PATH = new OneShot<ExecutionPath>(new ReleaseExecutionPath()); // Release is the default execution path
static boolean mPrintDebugLogs = unBox(SHOULD_PRINT_DEBUG_LOGS.get()); // Do not set the value from anywhere other than turnOn/Off logs. Just meant for easy reference.
static {
TAG_HELPER = new TagHelper();
OBJECT_POOL = new LogTokenPool();
}
private Snippet() {
}
/**
* Installs custom execution path. It is best to call this as early as possible.
* Prior to this call, core functionality will be routed to Release Execution path which is
* default. Can be set once. Attempts to set it multiple times will not be honoured.
*
* @param path execution path.
*/
public static void install(ExecutionPath path) {
EXECUTION_PATH.set(path);
}
/**
* Captures a closure which needs to be measured.
*
* @param message Custom message if any.
* @param closure Lambda or implementation representing the closure.
*/
public static void capture(String message, Closure closure) {
EXECUTION_PATH.get().capture(message, closure);
}
/**
* Captures a closure which needs to be measured when no custom messages are required.
*
* @param closure Lambda or implementation representing the closure.
*/
public static void capture(Closure closure) {
EXECUTION_PATH.get().capture(null, closure);
}
/**
* Snippet identifies the execution context by analysing the stack frames and examining private
* members of {@link StackTraceElement} class. Out of dozens of stack frames containing JDK
* and android SDK frames, it has to identify the last frame which was related to the user application.
* For that uses the regex matching this filter to identify the user's application frame.
* After receiving the new regex it replaces the old stack analyser component with the new one.
* This impacts stack analyser of the log token also. Any log token created after this call will
* use the new regex, but token created before this call will have this one.
*
* @param regex REGEX identifying the application.
*/
public static void setPackageRegex(String regex) {
packageNameFilter = regex;
stackAnalyser = new StackAnalyser(packageNameFilter);
}
private static ExecutionContext invokeMeasureAndAttachExecutionContext(String message, Closure closure) {
long delta = ToolBox.invokeAndMeasure(closure);
ExecutionContext executionContext = getExecutionContext();
executionContext.setExecutionDuration(delta);
// Build the log string using the snippet info we got.
StringBuilder logMessageBuilder = new StringBuilder();
if (message != null && !message.isEmpty()) {
logMessageBuilder.append(message).append("::");
}
appendExecutionContextToLog(logMessageBuilder, executionContext);
logMessageBuilder.append(SEPARATOR).append('(').append(delta).append(" ms)");
Log.d(primaryFilter, logMessageBuilder.toString());
return executionContext;
}
/**
* Returns the execution context in te form of SnippetInfo class. That can be returned and
* used by external clients too.
*
* @return snippet info.
*/
private static ExecutionContext getExecutionContext() {
Thread thread = Thread.currentThread();
ExecutionContext info = new ExecutionContext();
info.setClassName(stackAnalyser.callingClass(thread, StackAnalyser.API_CAPTURE));
info.setMethod(stackAnalyser.callingMethod(thread, StackAnalyser.API_CAPTURE));
info.setLineNo(stackAnalyser.callingLine(thread, StackAnalyser.API_CAPTURE));
info.setThreadName(thread.getName());
return info;
}
private static void appendExecutionContextToLog(StringBuilder logMessageBuilder, ExecutionContext context) {
if (testFlag(FLAG_METADATA_CLASS)) {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "FLAG_METADATA_CLASS set");
}
String trimmedClass = trimPackageFromClass(context.getClassName());
logMessageBuilder.append("[Class = ").append(trimmedClass).append(']').append(SEPARATOR);
}
if (testFlag(FLAG_METADATA_METHOD)) {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "FLAG_METADATA_METHOD set");
}
logMessageBuilder.append("[Method = ").append(context.getMethodName()).append(']').append(SEPARATOR);
}
if (testFlag(FLAG_METADATA_LINE)) {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "FLAG_METADATA_LINE set");
}
logMessageBuilder.append("<Line no. ").append(context.getLineNo()).append('>').append(SEPARATOR);
}
if (testFlag(FLAG_METADATA_THREAD_INFO)) {
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "FLAG_METADATA_THREAD_INFO set");
}
logMessageBuilder.append("[Thread name = ").append(Thread.currentThread().getName()).append(']').append(SEPARATOR);
}
}
private static String trimPackageFromClass(String qualifiedName) {
String[] tokens = qualifiedName.split("\\.");
if (Snippet.mPrintDebugLogs) {
StringBuilder temp = new StringBuilder();
for (String a : tokens) {
temp.append('[').append(a).append("] ");
}
Log.d(TAG, "trimPackageFromClass() tokens: " + temp.toString());
}
return tokens[tokens.length - 1];
}
/**
* Set a new global filter and returns the old filter, just in case if you need to restore it.
*
* @param newFilterKey new filter for logcat
* @return old filter value.
*/
public static String newFilter(String newFilterKey) {
String oldTag = primaryFilter;
primaryFilter = newFilterKey;
return oldTag;
}
/**
* Adds a flag which determines the execution context shown on the logs such as class name, method
* and line no getting called on the logcat output. Supports
* <ol>
* * <li>{@link Snippet#FLAG_METADATA_CLASS}</li>
* * <li>{@link Snippet#FLAG_METADATA_METHOD}</li>
* * <li>{@link Snippet#FLAG_METADATA_LINE}</li>
* * <li>{@link Snippet#FLAG_NONE}</li>
* * </ol>
*
* @param flag Integer representing the flags
* @return new flag value.
*/
public static int addFlag(int flag) {
//assureCorrectFlag(flag); // This will prevent us from supplying compound flags.
mFlags |= flag;
return mFlags;
}
private static void assureCorrectFlag(int flag) {
if (flag != 1 << 31 && flag != 1 << 30 && flag != 1 << 29 && flag != 1 << 28) {
throw new IllegalArgumentException("Please set a valid flag");
}
if (Snippet.mPrintDebugLogs) {
Log.d(TAG, "Flag validation completed.");
}
}
/**
* Checks whether a particular flag is set in the Snippet or not.
*
* @param flag Flag to test
* @return true if set, false otherwise.
*/
public static boolean testFlag(int flag) {
assureCorrectFlag(flag);
return (mFlags & flag) == flag;
}
/**
* Clears the flag which shows execution context.
* Calling this will not show any execution context in the logs.
*/
public static void clearFlags() {
mFlags = FLAG_NONE;
}
/**
* Starts the measurement of code spread across multiple classes and methods. It returns a
* LogToken which can be passed across class and methods. To end the measurement call {@link LogToken#endCapture()}
*
* @return LogToken
*/
@NonNull
public static ILogToken startCapture() {
return EXECUTION_PATH.get().startCapture();
}
/**
* Starts the measurement of code spread across multiple classes and methods. It returns a
* LogToken which can be passed as well as retrieved using a tag which is provided at the
* time of token creation. It enables a direct access for the log token anywhere in the code.
* If a token with the provided tag already exists, this call with return null which will make
* further calls to {@link LogToken#endCapture()} crash.
* If multiple threads simultaneously try to call this method with same tag, the first one to acquire the tag
* wins and the other gets Pair object with supplied log token and a successFlag as false .
* The token acquired in the initial step of this method is returned to the pool.
*
* @param tag tag
* @return LogToken which can we retrieved using the tag if request successful, null otherwise
*/
@Nullable
public static ILogToken startCapture(String tag) {
return EXECUTION_PATH.get().startCapture(tag);
}
/**
* Finds a token with provided tag. The tag should match the tag provided with
* {@link Snippet#startCapture(String)} or else Attenuated token is returned.
*
* @param tag Tag
* @return LogToken if existing, attenuated token otherwise.
*/
public static ILogToken find(String tag) {
return EXECUTION_PATH.get().find(tag);
}
public static void turnOnLogging() {
SHOULD_PRINT_DEBUG_LOGS.set(true);
mPrintDebugLogs = unBox(SHOULD_PRINT_DEBUG_LOGS.get());
}
public static void turnOffLogging() {
SHOULD_PRINT_DEBUG_LOGS.set(false);
mPrintDebugLogs = unBox(SHOULD_PRINT_DEBUG_LOGS.get());
}
private static boolean unBox(Boolean aBoolean) {
if (aBoolean == null) {
return false;
}
return aBoolean;
}
/**
* Piece of code whose execution times need to be measured.
*/
public interface Closure {
/**
* Invokes the code which is passed inside the closure.
* Users are not supposed to call this method directly.
*/
void invoke();
}
/**
* When the execution spreads across multiple classes and methods, and lambda cannot be used
* to capture the code, then LogToken is created by calling {@link Snippet#startCapture()}, the
* measurement start from this point, now we can pass the token anywhere
* and, will end the measurement when {@link LogToken#endCapture()} is called.
* Default filter for a log token is the global filter of Snippet.
*/
public static class LogToken implements ILogToken {
private static final String TAG = LogToken.class.getSimpleName();
private static final String SPLIT_MESSAGE = "********SPLIT[" + "%1s" + "]"
+ SEPARATOR + "(" + "%2s" + " " + "ms"
+ ")" + "********";
private final StackAnalyser mLocalAnalyser = new StackAnalyser(packageNameFilter);
private long mStartTime;
private long mEndTime;
private String mFilter;
private long mThreadId = -1L;
private boolean mThreadLockEnabled = false;
private volatile LogTokenState mState;
private long mLastSplitTimeCaptured = 0L;
private List<Split> mSplitRecord;
// To be called only through LogTokenPool. Should not be created through any other ways.
protected LogToken() {
if (mPrintDebugLogs) {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
Log.d(TAG, "Inside LogToken's <init>. startCapture() called on the main thread. LogToken[" + this.toString() + "]");
} else {
Log.d(TAG, "Inside LogToken's <init>. startCapture() called off the main thread. LogToken[" + this.toString() + "]");
}
Log.d(TAG, "<init> will be called once per LogToken object, after the usage is over, it will be returned to the pool and will get recycled using obtain()");
}
this.mStartTime = ToolBox.currentTime();
this.mFilter = Snippet.primaryFilter; // Uses the primary filter by default.
}
/**
* Can be used to override the Global filter provided by Snippet.
* Once this LogToken's endCapture() is called, the filter is reset to
* the global filter again.
*
* @param newFilter new filter
* @return LogToken instance on which start()/end() can be called.
*/
@Override
public LogToken overrideFilter(String newFilter) {
this.mFilter = newFilter;
return this;
}
/**
* Returns current filter
*
* @return existing filter
*/
@Override
public String filter() {
return this.mFilter;
}
@Override
public long creatorThreadId() {
return this.mThreadId;
}
@Override
public boolean isThreadLockEnabled() {
return mThreadLockEnabled;
}
@Override
public ILogToken enableThreadLock() {
mThreadLockEnabled = true;
return this;
}
public void setCreatorThreadId(long id) {
this.mThreadId = id;
}
@Override
public void reset() {
this.mStartTime = 0;
this.mEndTime = 0;
this.mFilter = Snippet.primaryFilter;
this.mThreadId = -1L;
this.mThreadLockEnabled = false;
if(this.mSplitRecord != null) {
this.mSplitRecord.clear();
}
this.mSplitRecord = null;
}
/**
* Creates a split within the span of LogToken's startCapture() and endCapture() methods.
* Calling addSplit() will measure the time taken since the last time addSplit() was called.
* If addSplit() is called for the first time then it would measure the time between
* {@link Snippet#startCapture()} and {@link LogToken#endCapture()}. Calling addSplit() after
* endCapture() is called would lead to IllegalStateException.
*/
@Override
public void addSplit() {
Split newSplit = addSplitInternal();
Log.d(mFilter, String.format(SPLIT_MESSAGE, newSplit.sequence(), newSplit.delta()));
}
/**
* Does the same thing as {@link LogToken#addSplit()}. It just additionally adds a message string
* with the Split.
*
* @param message Custom message to print on the log cat.
*/
@Override
public void addSplit(String message) {
Split newSplit = addSplitInternal();
newSplit.setName(message);
Log.d(mFilter, String.format(SPLIT_MESSAGE, "[" + newSplit.sequence() + "]" + message, newSplit.delta()));
}
private Split addSplitInternal() {
// This is not fully true but a quick hack, will have to think about multiple states possible.
if (mState != LogTokenState.ACTIVE) {
throw new IllegalStateException("addSplit() called after endCapture() is executed! Development error!!!!!!");
}
Split newSplit = createSplit();
synchronized (this) {
if (mSplitRecord == null) {
mSplitRecord = new ArrayList<>();
}
mSplitRecord.add(newSplit);
}
return newSplit;
}
private Split createSplit() {
Split newSplit;
synchronized (this) {
if (mState != LogTokenState.ACTIVE) {
throw new IllegalStateException("addSplit() called after endCapture() is executed! Development error!!!!!!");
}
if (mLastSplitTimeCaptured == 0L) { // Split called for the first time
// We use the token start time as the reference.
mLastSplitTimeCaptured = ToolBox.currentTime();
newSplit = new Split(getStart(), mLastSplitTimeCaptured);
} else {
// Here we use the last split time captured.
long currentTime = ToolBox.currentTime();
newSplit = new Split(mLastSplitTimeCaptured, currentTime);
mLastSplitTimeCaptured = currentTime;
}
}
return newSplit;
}
@Override
public void setState(LogTokenState state) {
mState = state;
}
@Override
public LogTokenState getState() {
return mState;
}
/**
* Ends the capture which was started through {@link Snippet#startCapture()}.
*/
@Override
public ExecutionContext endCapture() {
synchronized (this) {
if (mState == LogTokenState.END_CAPTURE_EXECUTED) {
return Snippet.EMPTY_CONTEXT;
}
ExecutionContext executionContext;
if (ToolBox.willThreadLockGuardThisCapture(Thread.currentThread(), this)) {
Log.e(TAG, "ThreadLocks enabled! Not able to end the capture as the token "
+ "creating thread is not same as the thread calling endCapture().");
return Snippet.EMPTY_CONTEXT;
} else {
mState = LogTokenState.END_CAPTURE_EXECUTED;
executionContext = doEndSlice(null);
if (mSplitRecord != null && mSplitRecord.size() > 0) {
dumpSplitData(mSplitRecord, executionContext);
}
ILogToken token = TAG_HELPER.unTag(this);
if (token == null) {
Log.e(TAG, "Not able to unTag as the tag for the request was not available.");
}
OBJECT_POOL.recycle(this);
return executionContext;
}
}
}
private void dumpSplitData(List<Split> splits, ExecutionContext context) {
StringBuilder recordSummaryBuilder = new StringBuilder('\n');
recordSummaryBuilder.append(" Split Summary").append('\n');
recordSummaryBuilder.append(" *********************************************************************************************"
+ "*************************************");
for (Split split : splits) {
recordSummaryBuilder.append('\n').append('|').append("___").append("Split[").append(split.sequence()).append(']');
if (split.getName() != null && !TextUtils.isEmpty(split.getName())) {
recordSummaryBuilder.append('[').append(split.getName()).append(']').append(' ');
}
recordSummaryBuilder.append(split.delta()).append('/').append(context.getExecutionDuration()).append(" ( ms ) ");
recordSummaryBuilder.append(" ").append('(').append(String.format(Locale.US, "%.3f", split.percentage(context.getExecutionDuration()))).append(" %").append(')');
recordSummaryBuilder.append(" of total capture.");
}
recordSummaryBuilder.append("\n *************************************************************************************************************************************");
Log.d(mFilter, recordSummaryBuilder.toString());
}
/**
* Ends the capture which was started through {@link Snippet#startCapture()}.
*
* @param message Custom message if required
*/
@Override
public ExecutionContext endCapture(String message) {
synchronized (this) {
if (mState == LogTokenState.END_CAPTURE_EXECUTED) {
return Snippet.EMPTY_CONTEXT;
}
ExecutionContext executionContext;
if (ToolBox.willThreadLockGuardThisCapture(Thread.currentThread(), this)) {
Log.e(TAG, mFilter + " ThreadLocks enabled! Not able to end the capture as the"
+ " token creating thread is not same as the thread calling endCapture(message).");
return Snippet.EMPTY_CONTEXT;
} else {
mState = LogTokenState.END_CAPTURE_EXECUTED;
executionContext = doEndSlice(message);
if (mSplitRecord != null && mSplitRecord.size() > 0) {
dumpSplitData(mSplitRecord, executionContext);
}
ILogToken token = TAG_HELPER.unTag(this);
if (token == null) {
Log.d(TAG, "Not able to unTag as the tag for the request was not available.");
}
OBJECT_POOL.recycle(this);
return executionContext;
}
}
}
private ExecutionContext doEndSlice(String message) {
synchronized (this) {
mEndTime = ToolBox.currentTime();
long delta = mEndTime - mStartTime;
StringBuilder logMessageBuilder = new StringBuilder();
if (message != null && !message.isEmpty()) {
logMessageBuilder.append(message).append(SEPARATOR);
}
Thread thread = Thread.currentThread();
if (mPrintDebugLogs) {
if (thread == Looper.getMainLooper().getThread()) {
Log.d(TAG, "endCapture() called on the main thread. LogToken[" + this.toString() + "]");
} else {
Log.d(TAG, "endCapture() called off the main thread");
}
}
ExecutionContext executionContext = new ExecutionContext();
executionContext.setClassName(mLocalAnalyser.callingClass(thread, StackAnalyser.API_LOG_TOKEN));
executionContext.setMethod(mLocalAnalyser.callingMethod(thread, StackAnalyser.API_LOG_TOKEN));
executionContext.setLineNo(mLocalAnalyser.callingLine(thread, StackAnalyser.API_LOG_TOKEN));
executionContext.setThreadName(thread.getName());
executionContext.setExecutionDuration(delta);
appendExecutionContextToLog(logMessageBuilder, executionContext);
logMessageBuilder.append(SEPARATOR).append('(').append(delta).append(" ms)");
Log.d(mFilter, logMessageBuilder.toString());
return executionContext;
}
}
@Override
public final long getStart() {
return mStartTime;
}
@Override
public final long getEnd() {
return mEndTime;
}
@Override
public final void setStart(long start) {
this.mStartTime = start;
}
@Override
public final void setEnd(long start) {
this.mStartTime = start;
}
}
/**
* Execution path that is used by Snippet to route the code to the core library functionality.
*/
public static class MeasuredExecutionPath implements ExecutionPath {
@Override
@NonNull
public ExecutionContext capture(String message, Closure closure) {
return invokeMeasureAndAttachExecutionContext(message, closure);
}
@Override
@NonNull
public ExecutionContext capture(Closure closure) {
return invokeMeasureAndAttachExecutionContext(null, closure);
}
@Override
public ILogToken startCapture() {
long startTime = ToolBox.currentTime();
ILogToken token = OBJECT_POOL.obtain();
token.setStart(startTime);
token.setCreatorThreadId(Thread.currentThread().getId());
return token;
}
@Override
public ILogToken startCapture(String tag) {
long startTime = ToolBox.currentTime();
ILogToken token = OBJECT_POOL.obtain();
token.setStart(startTime);
token.setCreatorThreadId(Thread.currentThread().getId());
Pair<ILogToken, Boolean> tagResult = TAG_HELPER.tag(tag, token);
if (!tagResult.getSecond()) {
Log.e(TAG, "Tag: [" + tag + "] already exists in the record, cannot assign log token, so we are providing a NO_OP_TOKEN.");
OBJECT_POOL.recycle(token);
return NO_OP_TOKEN;
}
if (mPrintDebugLogs) {
Log.e(TAG, "Tag: [" + tag + "] created for the LogToken.");
}
return token;
}
/**
* Used to find Log Token that was created using {@link Snippet#startCapture(String)}
* If we try to find a tag that does not exist it would return {@link Snippet#NO_OP_TOKEN}
* that is a NO-OP implementation of {@link ILogToken} interface.
*
* @param tag Custom tag. This tag will be used to search the log token across the app.
* @return LogToken if existing with the tag, No op token otherwise.
*/
@Override
public ILogToken find(String tag) {
ILogToken token = TAG_HELPER.search(tag);
if (token == null) {
return NO_OP_TOKEN;
} else {
return token;
}
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
/**
* Contains the list of areas that are tracked by Snippet. These will help getting the before/after
* numbers while doing the PR review.
*/
public final class SnippetConstants {
private SnippetConstants() {
// private
}
// TTE stands for Time to Execute
public static final String SUPER_ON_MAM_CREATE = "TTE super.onMamCreate()";
public static final String INIT_SKYPE_DB_HELPER = "TTE SkypeTeamsDBHelper";
public static final String INIT_BACKGROUND_OBJ = "TTE Initialize background objects";
public static final String INIT_NOTIFICATION_CHANNEL_HELPER = "TTE Initialize notificationChannelHelper";
public static final String INIT_NOTIFICATION_MGR = "TTE Initialize notification manager";
public static final String SUPER_MA_ON_CREATE = "TTE super.onCreate() MA";
public static final String INIT_SEARCH_BAR_VIEW = "TTE SearchBarView MA";
public static final String SETUP_CONTENT_VIEW = "TTE SetupContentView";
public static final String INJECT_DEPENDENCIES = "TTE injectIfNecessary";
public static final String SETUP_TOOLBAR = "TTE setupToolBar";
public static final String MA_LOAD_INACTIVE_TABS_UI_THREAD_WORK = "TTE loadInactiveTabs() UI thread Work";
public static final String MA_LOAD_SELECTED_FRAGMENT = "TTE load selected fragment";
public static final String DEPENDENCY_INJECTION = "TTE Dependency injection";
public static final String BOTTOM_BAR_APP_CLICK_TELEMETRY = "Bottom Bar App Click Telemetry";
}

Просмотреть файл

@ -0,0 +1,56 @@
package com.microsoft.snippet;
import java.util.concurrent.atomic.AtomicInteger;
public class Split {
private static final AtomicInteger SEQUENCE = new AtomicInteger(1);
private final long mStarted;
private final long mEnded;
private final int mSequence;
private String mName;
private String mInfo;
public Split(long start, long end) {
this.mStarted = start;
this.mEnded = end;
this.mSequence = SEQUENCE.getAndIncrement();
}
public void setName(String name) {
this.mName = name;
}
public String getName() {
return this.mName;
}
public long getStarted() {
return mStarted;
}
public long getEnded() {
return mEnded;
}
public long delta() {
return mEnded - mStarted;
}
public String getInfo() {
return mInfo;
}
public void setInfo(String mInfo) {
this.mInfo = mInfo;
}
public int sequence() {
return this.mSequence;
}
public double percentage(long total) {
return ((double) delta() / total) * 100;
}
}

Просмотреть файл

@ -0,0 +1,76 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
/**
* Internal helper class used to extract the execution context of the code which was guarded
* by Snippet APIs.
* NOT FOR EXTERNAL USE
*/
final class StackAnalyser {
static final int API_CAPTURE = 0;
static final int API_LOG_TOKEN = 1;
private final String mPackage;
StackAnalyser(String packageName) {
this.mPackage = packageName;
}
StackAnalyser() {
this("com.microsoft");
}
String callingMethod(Thread mCallingThread, int apiType) {
if (apiType == API_CAPTURE) {
return getCallingFrameForCapture(mCallingThread, apiType).getMethodName();
} else {
return getDoEndSliceCallerFrame(mCallingThread, apiType).getMethodName();
}
}
String callingClass(Thread mCallingThread, int apiType) {
if (apiType == API_CAPTURE) {
return getCallingFrameForCapture(mCallingThread, apiType).getClassName();
} else {
return getDoEndSliceCallerFrame(mCallingThread, apiType).getClassName();
}
}
int callingLine(Thread mCallingThread, int apiType) {
if (apiType == API_CAPTURE) {
return getCallingFrameForCapture(mCallingThread, apiType).getLineNumber();
} else {
return getDoEndSliceCallerFrame(mCallingThread, apiType).getLineNumber();
}
}
private StackTraceElement searchForCallingFrameOfMethodCaller(Thread callingThread, String methodName, int apiType) {
StackTraceElement[] frames = callingThread.getStackTrace();
int index = -1;
for (int i = 0; i < frames.length; i++) {
if (frames[i].getMethodName().equals(methodName)
&& frames[i].getClassName().startsWith(mPackage) && apiType == API_CAPTURE
&& frames[i].getClassName().equals("com.microsoft.snippet.Snippet")
||
frames[i].getMethodName().equals(methodName)
&& frames[i].getClassName().startsWith(mPackage) && apiType == API_LOG_TOKEN
&& frames[i].getClassName().equals("com.microsoft.snippet.Snippet$LogToken")) {
index = i;
break;
}
}
// Now we know the index at which we found the method in the stackframe, the next stack frame belongs to the code that called that method.
return callingThread.getStackTrace()[index + 1];
}
private StackTraceElement getDoEndSliceCallerFrame(Thread thread, int apiType) {
return searchForCallingFrameOfMethodCaller(thread, "endCapture", apiType);
}
private StackTraceElement getCallingFrameForCapture(Thread thread, int apiType) {
return searchForCallingFrameOfMethodCaller(thread, "capture", apiType);
}
}

Просмотреть файл

@ -0,0 +1,93 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
import android.util.Log;
import androidx.annotation.NonNull;
import com.microsoft.snippet.token.ILogToken;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Internal helper class that takes care of tagging the LogTokens.
* Not intended for external use.
*
* @author vishalratna
*/
class TagHelper implements ILogTokenSearcher {
private static final String LOG_TAG = TagHelper.class.getSimpleName();
private final Map<String, ILogToken> mRegistry;
TagHelper() {
this.mRegistry = new HashMap<>();
}
@NonNull
Pair<ILogToken, Boolean> tag(String tag, ILogToken token) {
synchronized (mRegistry) {
// Synchronizing to make sure, while we are checking the existence, a new thread does
// not add a key which returns true for contains() call.
if (mRegistry.containsKey(tag)) {
if (Snippet.mPrintDebugLogs) {
Log.e(LOG_TAG, "Tag already existing, we will not provide tagged token. Returning the token without tagging.");
}
return new Pair<>(token, false);
}
// Now we know that entry is not existing, make an entry
if (Snippet.mPrintDebugLogs) {
Log.d(LOG_TAG, "Tagging the LogToken and returning it back");
}
mRegistry.put(tag, token);
}
return new Pair<>(token, true);
}
ILogToken unTag(ILogToken token) {
synchronized (mRegistry) {
String requestedTag = null;
Set<Map.Entry<String, ILogToken>> entrySet = mRegistry.entrySet();
for (Map.Entry<String, ILogToken> eachEntry : entrySet) {
if (eachEntry.getValue() == token) {
requestedTag = eachEntry.getKey();
break;
}
}
// If we did not get anything after searching the registry.
if (requestedTag == null) {
if (Snippet.mPrintDebugLogs) {
Log.e(LOG_TAG, "There is no such tagged LogToken existing. Bad request! Returning");
}
return null;
}
if (Snippet.mPrintDebugLogs) {
Log.e(LOG_TAG, "We found a existing tag for the LogToken " + token.toString() + ", removing the tag");
}
return mRegistry.remove(requestedTag);
}
}
@Override
public ILogToken search(String tag) {
synchronized (mRegistry) {
if (mRegistry.containsKey(tag)) {
return mRegistry.get(tag);
} else {
if (Snippet.mPrintDebugLogs) {
Log.d(LOG_TAG, "There is no log token with tag: " + tag);
}
return null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
import android.os.SystemClock;
import com.microsoft.snippet.token.ILogToken;
/**
* Utility class the houses some helper functions used across the library.
*/
final class ToolBox {
private ToolBox() {
}
static long invokeAndMeasure(Snippet.Closure closure) {
long start = SystemClock.uptimeMillis();
closure.invoke();
long end = SystemClock.uptimeMillis();
return end - start;
}
static boolean willThreadLockGuardThisCapture(Thread currentThread, ILogToken token) {
if (token.isThreadLockEnabled()) {
return currentThread.getId() != token.creatorThreadId();
} else {
return false;
}
}
static String combineThreadIdWithUserTag(String tag) {
return tag + ":" + Thread.currentThread().getId();
}
static long getThreadIdFromSnippetTag(String snippetTag) {
String[] items = snippetTag.split(":");
if (items.length != 2) {
throw new IllegalStateException("Snippet tag should have 2 items, user tag and thread ID");
}
return Long.parseLong(items[1]);
}
static String getUserTagFromSnippetTag(String snippetTag) {
String[] items = snippetTag.split(":");
if (items.length != 2) {
throw new IllegalStateException("Snippet tag should have 2 items, user tag and thread ID");
}
return items[0];
}
static long currentTime() {
return SystemClock.uptimeMillis();
}
}

Просмотреть файл

@ -0,0 +1,36 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet;
/**
* Represents a Triple of objects
*
* @param <A> A
* @param <B> B
* @param <C> C
*/
public final class Triple<A, B, C> {
A a;
B b;
C c;
public Triple(A first, B second, C third) {
this.a = first;
this.b = second;
this.c = third;
}
public A getFirst() {
return a;
}
public B getSecond() {
return b;
}
public C getThird() {
return c;
}
}

Просмотреть файл

@ -0,0 +1,105 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet.token;
import com.microsoft.snippet.ExecutionContext;
/**
* Attenuated token represents a LogToken with suppressed functionality.
* This is helpful in
* 1. Creating NO-OP implementations for our public facing APIs.
* 2. While using {@link com.microsoft.snippet.Snippet#find(String)} it might be possible that we
* ask for a tag that does not exist it would lead to a null result and would require the caller
* to use a null check. Using this there helps in returning a no op token and user can avoid null check
* 3. In some flows you won't always find logtokens for example,
* while creating a token with tag in onCreate() of Application class and trying to fetch it in
* some activity. As application onCreate() will not be called always, activity might need a
* null check after find call, that is avoided by the use of this class.
*/
public final class AttenuatedLogToken implements ILogToken {
private static final ExecutionContext NONE_INFO = new ExecutionContext();
public AttenuatedLogToken() {
}
@Override
public ExecutionContext endCapture() {
return NONE_INFO;
}
@Override
public long getStart() {
return -1;
}
@Override
public long getEnd() {
return -1;
}
@Override
public void setStart(long start) {
}
@Override
public void setEnd(long start) {
}
@Override
public ILogToken overrideFilter(String newFilter) {
return this;
}
@Override
public String filter() {
return "";
}
@Override
public ExecutionContext endCapture(String message) {
return NONE_INFO;
}
@Override
public long creatorThreadId() {
return -1L;
}
@Override
public boolean isThreadLockEnabled() {
return false;
}
@Override
public ILogToken enableThreadLock() {
return this;
}
@Override
public void setCreatorThreadId(long threadId) {
}
@Override
public void reset() {
}
@Override
public void addSplit() {
}
@Override
public void addSplit(String message) {
}
@Override
public void setState(LogTokenState state) {
}
@Override
public LogTokenState getState() {
return LogTokenState.ATTENUATED;
}
}

Просмотреть файл

@ -0,0 +1,74 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet.token;
import com.microsoft.snippet.Snippet;
import com.microsoft.snippet.ExecutionContext;
/**
* Representation of log token. All types of log token should implement this interface
*/
public interface ILogToken {
/**
* Set the filter for this log token, this is override the global filter.
*
* @param newFilter new filter
* @return IToken instance on which start()/end() can be called.
*/
ILogToken overrideFilter(String newFilter);
/**
* Returns existing filter
*
* @return existing filter
*/
String filter();
/**
* Ends the capture which was started through {@link Snippet#startCapture()}.
*
* @param message Custom message if required
*/
ExecutionContext endCapture(String message);
/**
* Returns the id of the thread that asked for this log token.
*
* @return Thread ID.
*/
long creatorThreadId();
/**
*
*/
boolean isThreadLockEnabled();
ILogToken enableThreadLock();
void setCreatorThreadId(long threadId);
void reset();
void addSplit();
void addSplit(String message);
void setState(LogTokenState state);
LogTokenState getState();
/**
* Ends the capture which was started through {@link Snippet#startCapture()}.
*/
ExecutionContext endCapture();
long getStart();
long getEnd();
void setStart(long start);
void setEnd(long start);
}

Просмотреть файл

@ -0,0 +1,26 @@
/*
* Copyright © Microsoft Corporation. All rights reserved.
*/
package com.microsoft.snippet.token;
public enum LogTokenState {
ACTIVE(1),
END_CAPTURE_EXECUTED(2),
IN_POOL(3),
ATTENUATED(4);
private final int mId;
LogTokenState(int id) {
this.mId = id;
}
public int getID() {
return mId;
}
}