Merge "Infrastructure for showing instant app metadata in app header"
diff --git a/res/layout/app_details.xml b/res/layout/app_details.xml
index 9f349de..3088865 100644
--- a/res/layout/app_details.xml
+++ b/res/layout/app_details.xml
@@ -75,6 +75,41 @@
 
     </LinearLayout>
 
+    <TextView
+        android:id="@+id/instant_app_developer_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:visibility="gone"/>
+
+    <LinearLayout
+        android:id="@+id/instant_app_maturity"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:visibility="gone">
+
+        <ImageView
+            android:id="@+id/instant_app_maturity_icon"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:scaleType="fitXY"/>
+        <TextView
+            android:id="@+id/instant_app_maturity_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/instant_app_monetization"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:visibility="gone"/>
+
+
     <LinearLayout
         android:id="@+id/app_detail_links"
         android:layout_width="match_parent"
diff --git a/src/com/android/settings/applications/AppHeaderController.java b/src/com/android/settings/applications/AppHeaderController.java
index a6321df..5b243ff 100644
--- a/src/com/android/settings/applications/AppHeaderController.java
+++ b/src/com/android/settings/applications/AppHeaderController.java
@@ -37,6 +37,7 @@
 import com.android.settings.AppHeader;
 import com.android.settings.R;
 import com.android.settings.Utils;
+import com.android.settings.applications.instantapps.InstantAppDetails;
 import com.android.settingslib.applications.ApplicationsState;
 
 import java.lang.annotation.Retention;
@@ -78,6 +79,8 @@
     @ActionType
     private int mRightAction;
 
+    private InstantAppDetails mInstantAppDetails;
+
     public AppHeaderController(Context context, Fragment fragment, View appHeader) {
         mContext = context;
         mFragment = fragment;
@@ -147,6 +150,11 @@
         return this;
     }
 
+    public AppHeaderController setInstantAppDetails(InstantAppDetails instantAppDetails) {
+        mInstantAppDetails = instantAppDetails;
+        return this;
+    }
+
     /**
      * Binds app header view and data from {@code PackageInfo} and {@code AppEntry}.
      */
@@ -207,6 +215,29 @@
         if (rebindActions) {
             bindAppHeaderButtons();
         }
+
+        if (mInstantAppDetails != null) {
+            setText(R.id.instant_app_developer_title, mInstantAppDetails.developerTitle);
+            View maturity = mAppHeader.findViewById(R.id.instant_app_maturity);
+
+            if (maturity != null) {
+                String maturityText = mInstantAppDetails.maturityRatingString;
+                Drawable maturityIcon = mInstantAppDetails.maturityRatingIcon;
+                if (!TextUtils.isEmpty(maturityText) || maturityIcon != null) {
+                    maturity.setVisibility(View.VISIBLE);
+                }
+                setText(R.id.instant_app_maturity_text, maturityText);
+                if (maturityIcon != null) {
+                    ImageView maturityIconView = (ImageView) mAppHeader.findViewById(
+                            R.id.instant_app_maturity_icon);
+                    if (maturityIconView != null) {
+                        maturityIconView.setImageDrawable(maturityIcon);
+                    }
+                }
+            }
+            setText(R.id.instant_app_monetization, mInstantAppDetails.monetizationNotice);
+        }
+
         return mAppHeader;
     }
 
diff --git a/src/com/android/settings/applications/instantapps/InstantAppDetails.java b/src/com/android/settings/applications/instantapps/InstantAppDetails.java
new file mode 100644
index 0000000..8b54c20
--- /dev/null
+++ b/src/com/android/settings/applications/instantapps/InstantAppDetails.java
@@ -0,0 +1,110 @@
+/*
+ * 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.settings.applications.instantapps;
+
+import android.graphics.drawable.Drawable;
+import java.net.URL;
+
+/**
+ * Encapsulates state about instant apps that is provided by an app store implementation.
+ */
+public class InstantAppDetails {
+
+    // Most of these members are self-explanatory; the one that may not be is
+    // monetizationNotice, which is a string alerting users that the app contains ads and/or uses
+    // in-app purchases (this may eventually become two separate members).
+    public final Drawable maturityRatingIcon;
+    public final String maturityRatingString;
+    public final String monetizationNotice;
+    public final String developerTitle;
+    public final URL privacyPolicy;
+    public final URL developerWebsite;
+    public final String developerEmail;
+    public final String developerMailingAddress;
+
+    public static class Builder {
+        private Drawable mMaturityRatingIcon;
+        private String mMaturityRatingString;
+        private String mMonetizationNotice;
+        private String mDeveloperTitle;
+        private URL mPrivacyPolicy;
+        private URL mDeveloperWebsite;
+        private String mDeveloperEmail;
+        private String mDeveloperMailingAddress;
+
+        public Builder maturityRatingIcon(Drawable maturityRatingIcon) {
+            this.mMaturityRatingIcon = maturityRatingIcon;
+            return this;
+        }
+
+        public Builder maturityRatingString(String maturityRatingString) {
+            mMaturityRatingString = maturityRatingString;
+            return this;
+        }
+
+        public Builder monetizationNotice(String monetizationNotice) {
+            mMonetizationNotice = monetizationNotice;
+            return this;
+        }
+
+        public Builder developerTitle(String developerTitle) {
+            mDeveloperTitle = developerTitle;
+            return this;
+        }
+
+        public Builder privacyPolicy(URL privacyPolicy) {
+            mPrivacyPolicy = privacyPolicy;
+            return this;
+        }
+
+        public Builder developerWebsite(URL developerWebsite) {
+            mDeveloperWebsite = developerWebsite;
+            return this;
+        }
+
+        public Builder developerEmail(String developerEmail) {
+            mDeveloperEmail = developerEmail;
+            return this;
+        }
+
+        public Builder developerMailingAddress(String developerMailingAddress) {
+            mDeveloperMailingAddress = developerMailingAddress;
+            return this;
+        }
+
+        public InstantAppDetails build() {
+            return new InstantAppDetails(mMaturityRatingIcon, mMaturityRatingString,
+                    mMonetizationNotice, mDeveloperTitle, mPrivacyPolicy, mDeveloperWebsite,
+                    mDeveloperEmail, mDeveloperMailingAddress);
+        }
+    }
+
+    public static Builder builder() { return new Builder(); }
+
+    private InstantAppDetails(Drawable maturityRatingIcon, String maturityRatingString,
+            String monetizationNotice, String developerTitle, URL privacyPolicy,
+            URL developerWebsite, String developerEmail, String developerMailingAddress) {
+        this.maturityRatingIcon = maturityRatingIcon;
+        this.maturityRatingString = maturityRatingString;
+        this.monetizationNotice = monetizationNotice;
+        this.developerTitle = developerTitle;
+        this.privacyPolicy = privacyPolicy;
+        this.developerWebsite = developerWebsite;
+        this.developerEmail = developerEmail;
+        this.developerMailingAddress = developerMailingAddress;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/AppHeaderControllerTest.java b/tests/robotests/src/com/android/settings/applications/AppHeaderControllerTest.java
index 229ba45..1625e1c 100644
--- a/tests/robotests/src/com/android/settings/applications/AppHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/AppHeaderControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.settings.applications;
 
 
+import android.annotation.IdRes;
 import android.app.Activity;
 import android.app.Fragment;
 import android.content.Context;
@@ -24,15 +25,19 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.support.v7.preference.Preference;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
+import com.android.settings.applications.InstantDataBuilder.Param;
+import com.android.settings.applications.instantapps.InstantAppDetails;
 import com.android.settingslib.applications.ApplicationsState;
 
 import org.junit.Before;
@@ -51,6 +56,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.util.EnumSet;
+
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class AppHeaderControllerTest {
@@ -243,4 +250,103 @@
         assertThat(appLinks.findViewById(R.id.right_button).getVisibility())
                 .isEqualTo(View.GONE);
     }
+
+    // Ensure that no instant app related information shows up when the AppHeaderController's
+    // InstantAppDetails are null.
+    @Test
+    public void instantApps_nullInstantAppDetails() {
+        final View appHeader = mLayoutInflater.inflate(R.layout.app_details, null /* root */);
+        mController = new AppHeaderController(mContext, mFragment, appHeader);
+        mController.setInstantAppDetails(null);
+        mController.done();
+        assertThat(appHeader.findViewById(R.id.instant_app_developer_title).getVisibility())
+                .isEqualTo(View.GONE);
+        assertThat(appHeader.findViewById(R.id.instant_app_maturity).getVisibility())
+                .isEqualTo(View.GONE);
+        assertThat(appHeader.findViewById(R.id.instant_app_monetization).getVisibility())
+                .isEqualTo(View.GONE);
+    }
+
+    // Ensure that no instant app related information shows up when the AppHeaderController has
+    // a non-null InstantAppDetails, but each member of it is null.
+    @Test
+    public void instantApps_detailsMembersNull() {
+        final View appHeader = mLayoutInflater.inflate(R.layout.app_details, null /* root */);
+        mController = new AppHeaderController(mContext, mFragment, appHeader);
+
+        InstantAppDetails details = InstantDataBuilder.build(mContext, EnumSet.noneOf(Param.class));
+        mController.setInstantAppDetails(details);
+        mController.done();
+        assertThat(appHeader.findViewById(R.id.instant_app_developer_title).getVisibility())
+                .isEqualTo(View.GONE);
+        assertThat(appHeader.findViewById(R.id.instant_app_maturity).getVisibility())
+                .isEqualTo(View.GONE);
+        assertThat(appHeader.findViewById(R.id.instant_app_monetization).getVisibility())
+                .isEqualTo(View.GONE);
+    }
+
+    // Helper to assert a TextView for a given id is visible and has a certain string value.
+    private void assertVisibleContent(View header, @IdRes int id, String expectedValue) {
+        TextView view = (TextView)header.findViewById(id);
+        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(view.getText()).isEqualTo(expectedValue);
+    }
+
+    // Helper to assert an ImageView for a given id is visible and has a certain Drawable value.
+    private void assertVisibleContent(View header, @IdRes int id, Drawable expectedValue) {
+        ImageView view = (ImageView)header.findViewById(id);
+        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(view.getDrawable()).isEqualTo(expectedValue);
+    }
+
+    // Test that expected items are present in the header when we have a complete InstantAppDetails.
+    @Test
+    public void instantApps_expectedHeaderItems() {
+        final View header = mLayoutInflater.inflate(R.layout.app_details, null /* root */);
+        mController = new AppHeaderController(mContext, mFragment, header);
+
+        InstantAppDetails details = InstantDataBuilder.build(mContext);
+        mController.setInstantAppDetails(details);
+        mController.done();
+
+        assertVisibleContent(header, R.id.instant_app_developer_title, details.developerTitle);
+        assertVisibleContent(header, R.id.instant_app_maturity_icon,
+                details.maturityRatingIcon);
+        assertVisibleContent(header, R.id.instant_app_maturity_text,
+                details.maturityRatingString);
+        assertVisibleContent(header, R.id.instant_app_monetization,
+                details.monetizationNotice);
+    }
+
+    // Test having each member of InstantAppDetails be null.
+    @Test
+    public void instantApps_expectedHeaderItemsWithSingleNullMembers() {
+        final EnumSet<Param> allParams = EnumSet.allOf(Param.class);
+        for (Param paramToRemove : allParams) {
+            EnumSet<Param> params = allParams.clone();
+            params.remove(paramToRemove);
+            final View header = mLayoutInflater.inflate(R.layout.app_details, null /* root */);
+            mController = new AppHeaderController(mContext, mFragment, header);
+            InstantAppDetails details = InstantDataBuilder.build(mContext, params);
+            mController.setInstantAppDetails(details);
+            mController.done();
+
+            if (params.contains(Param.DEVELOPER_TITLE)) {
+                assertVisibleContent(header, R.id.instant_app_developer_title,
+                        details.developerTitle);
+            }
+            if (params.contains(Param.MATURITY_RATING_ICON)) {
+                assertVisibleContent(header, R.id.instant_app_maturity_icon,
+                        details.maturityRatingIcon);
+            }
+            if (params.contains(Param.MATURITY_RATING_STRING)) {
+                assertVisibleContent(header, R.id.instant_app_maturity_text,
+                        details.maturityRatingString);
+            }
+            if (params.contains(Param.MONETIZATION_NOTICE)) {
+                assertVisibleContent(header, R.id.instant_app_monetization,
+                        details.monetizationNotice);
+            }
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/applications/InstantDataBuilder.java b/tests/robotests/src/com/android/settings/applications/InstantDataBuilder.java
new file mode 100644
index 0000000..81ebe06
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/InstantDataBuilder.java
@@ -0,0 +1,118 @@
+/**
+ * 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.settings.applications;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+
+import com.android.settings.R;
+import com.android.settings.applications.instantapps.InstantAppDetails;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.EnumSet;
+
+/**
+ * Utility class for generating fake InstantAppDetails data to use in tests.
+ */
+public class InstantDataBuilder {
+    public enum Param {
+        MATURITY_RATING_ICON,
+        MATURITY_RATING_STRING,
+        MONETIZATION_NOTICE,
+        DEVELOPER_TITLE,
+        PRIVACY_POLICY,
+        DEVELOPER_WEBSITE,
+        DEVELOPER_EMAIL,
+        DEVELOPER_MAILING_ADDRESS
+    }
+
+    /**
+     * Creates an InstantAppDetails with any desired combination of null/non-null members.
+     *
+     * @param context An optional context, required only if MATURITY_RATING_ICON is a member of
+     * params
+     * @param params Specifies which elements of the returned InstantAppDetails should be non-null
+     * @return InstantAppDetails
+     */
+    public static InstantAppDetails build(@Nullable Context context, EnumSet<Param> params) {
+        Drawable ratingIcon = null;
+        String rating = null;
+        String monetizationNotice = null;
+        String developerTitle = null;
+        URL privacyPolicy = null;
+        URL developerWebsite = null;
+        String developerEmail = null;
+        String developerMailingAddress = null;
+
+        if (params.contains(Param.MATURITY_RATING_ICON)) {
+            ratingIcon = context.getDrawable(R.drawable.ic_android);
+        }
+        if (params.contains(Param.MATURITY_RATING_STRING)) {
+            rating = "everyone";
+        }
+        if (params.contains(Param.MONETIZATION_NOTICE)) {
+            monetizationNotice = "Uses in-app purchases";
+        }
+        if (params.contains(Param.DEVELOPER_TITLE)) {
+            developerTitle = "Instant Apps Inc.";
+        }
+        if (params.contains(Param.DEVELOPER_EMAIL)) {
+            developerEmail = "developer@instant-apps.com";
+        }
+        if (params.contains(Param.DEVELOPER_MAILING_ADDRESS)) {
+            developerMailingAddress = "1 Main Street, Somewhere, CA, 94043";
+        }
+
+        if (params.contains(Param.PRIVACY_POLICY)) {
+            try {
+                privacyPolicy = new URL("https://test.com/privacy");
+            } catch (MalformedURLException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        if (params.contains(Param.DEVELOPER_WEBSITE)) {
+            try {
+                developerWebsite = new URL("https://test.com");
+            } catch (MalformedURLException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        return InstantAppDetails.builder()
+                .maturityRatingIcon(ratingIcon)
+                .maturityRatingString(rating)
+                .monetizationNotice(monetizationNotice)
+                .developerTitle(developerTitle)
+                .privacyPolicy(privacyPolicy)
+                .developerWebsite(developerWebsite)
+                .developerEmail(developerEmail)
+                .developerMailingAddress(developerMailingAddress)
+                .build();
+    }
+
+    /**
+     * Convenience method to create an InstantAppDetails with all non-null members.
+     *
+     * @param context a required Context for loading a test maturity rating icon
+     * @return InstantAppDetails
+     */
+    public static InstantAppDetails build(Context context) {
+        return build(context, EnumSet.allOf(Param.class));
+    }
+}