[go: up one dir, main page]

Revert Revert "Expose chrome policies using Android's App Restrictions Schema"

Android generates alternate resources when dependent on a higher sdk version then the minsdkversion.
This is conflicts with Playstore rules that only permit one resource for App Restrictions.
I have now qualified the resource to apply only for sdk > 21 where the App Restriction API was exposed.

BUG=448829
BUG=446795

TBR=bartfab@chromium.org

Review URL: https://codereview.chromium.org/838263005

Cr-Commit-Position: refs/heads/master@{#311778}
(cherry picked from commit f946bbe6862f98c056288ec1242ed6aab9a7d717)

Review URL: https://codereview.chromium.org/935693002

Cr-Commit-Position: refs/branch-heads/2272@{#313}
Cr-Branched-From: 827a380cfdb31aa54c8d56e63ce2c3fd8c3ba4d4-refs/heads/master@{#310958}
diff --git a/components/policy.gypi b/components/policy.gypi
index ebfbcdd5..7e0533a4 100644
--- a/components/policy.gypi
+++ b/components/policy.gypi
@@ -12,6 +12,7 @@
     'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/chrome',
     'policy_out_dir': '<(SHARED_INTERMEDIATE_DIR)/policy',
     'protoc_out_dir': '<(SHARED_INTERMEDIATE_DIR)/protoc_out',
+    'android_resources_out_dir': '<(policy_out_dir)/android_resources',
     'generate_policy_source_script_path':
         'policy/tools/generate_policy_source.py',
     'policy_constant_header_path':
@@ -20,6 +21,10 @@
         '<(policy_out_dir)/policy/policy_constants.cc',
     'protobuf_decoder_path':
         '<(policy_out_dir)/policy/cloud_policy_generated.cc',
+    'app_restrictions_path':
+        '<(android_resources_out_dir)/xml-v21/app_restrictions.xml',
+    'app_resources_path':
+        '<(android_resources_out_dir)/values-v21/restriction_values.xml',
     # This is the "full" protobuf, which defines one protobuf message per
     # policy. It is also the format currently used by the server.
     'chrome_settings_proto_path':
@@ -109,6 +114,8 @@
                 '<(protobuf_decoder_path)',
                 '<(chrome_settings_proto_path)',
                 '<(cloud_policy_proto_path)',
+                '<(app_restrictions_path)',
+                '<(app_resources_path)',
               ],
               'action_name': 'generate_policy_source',
               'action': [
@@ -119,11 +126,21 @@
                 '--chrome-settings-protobuf=<(chrome_settings_proto_path)',
                 '--cloud-policy-protobuf=<(cloud_policy_proto_path)',
                 '--cloud-policy-decoder=<(protobuf_decoder_path)',
+                '--app-restrictions-definition=<(app_restrictions_path)',
+                '--app-restrictions-resources=<(app_resources_path)',
                 '<(OS)',
                 '<(chromeos)',
                 'policy/resources/policy_templates.json',
               ],
               'message': 'Generating policy source',
+              'conditions': [
+                ['OS!="android"', {
+                  'outputs!': [
+                    '<(app_restrictions_path)',
+                    '<(app_resources_path)',
+                  ],
+                }],
+              ],
             },
           ],
           'direct_dependent_settings': {
diff --git a/components/policy/BUILD.gn b/components/policy/BUILD.gn
index 5e405f7..d8d7c92 100644
--- a/components/policy/BUILD.gn
+++ b/components/policy/BUILD.gn
@@ -47,6 +47,9 @@
   # build puts everything into the following directory. We do the same for now.
   policy_gen_dir = "$root_gen_dir/policy"
 
+  # Directory for generating Android App Restrictions resources
+  android_resources_gen_dir = "$policy_gen_dir/android_resources"
+
   # This protobuf is equivalent to chrome_settings.proto but shares messages
   # for policies of the same type, so that less classes have to be generated
   # and compiled.
@@ -59,6 +62,10 @@
   constants_header_path = "$policy_gen_dir/policy_constants.h"
   constants_source_path = "$policy_gen_dir/policy_constants.cc"
   protobuf_decoder_path = "$policy_gen_dir/cloud_policy_generated.cc"
+  app_restrictions_path =
+      "$android_resources_gen_dir/xml-v21/app_restrictions.xml"
+  app_resources_path =
+      "$android_resources_gen_dir/values-v21/restriction_values.xml"
 
   action("cloud_policy_code_generate") {
     script = "tools/generate_policy_source.py"
@@ -78,8 +85,17 @@
       protobuf_decoder_path,
       chrome_settings_proto_path,
       cloud_policy_proto_path,
+      app_restrictions_path,
+      app_resources_path,
     ]
 
+    if (os != "android") {
+      outputs -= [
+        app_restrictions_path,
+        app_resources_path,
+      ]
+    }
+
     args = [
       "--policy-constants-header=" +
           rebase_path(constants_header_path, root_build_dir),
@@ -91,6 +107,10 @@
           rebase_path(cloud_policy_proto_path, root_build_dir),
       "--cloud-policy-decoder=" +
           rebase_path(protobuf_decoder_path, root_build_dir),
+      "--app-restrictions-definition=" +
+          rebase_path(app_restrictions_path, root_build_dir),
+      "--app-restrictions-resources=" +
+          rebase_path(app_resources_path, root_build_dir),
       os,
       chromeos_flag,
       rebase_path("resources/policy_templates.json", root_build_dir),
diff --git a/components/policy/tools/generate_policy_source.py b/components/policy/tools/generate_policy_source.py
index c3096c7..e343e33 100755
--- a/components/policy/tools/generate_policy_source.py
+++ b/components/policy/tools/generate_policy_source.py
@@ -18,6 +18,7 @@
 import sys
 import textwrap
 import types
+from xml.sax.saxutils import escape as xml_escape
 
 
 CHROME_POLICY_KEY = 'SOFTWARE\\\\Policies\\\\Google\\\\Chrome'
@@ -27,23 +28,34 @@
 class PolicyDetails:
   """Parses a policy template and caches all its details."""
 
-  # Maps policy types to a tuple with 3 other types:
+  # Maps policy types to a tuple with 5 other types:
   # - the equivalent base::Value::Type or 'TYPE_EXTERNAL' if the policy
   #   references external data
   # - the equivalent Protobuf field type
   # - the name of one of the protobufs for shared policy types
+  # - the equivalent type in Android's App Restriction Schema
+  # - whether the equivalent app restriction type needs supporting resources
   # TODO(joaodasilva): refactor the 'dict' type into a more generic 'json' type
   # that can also be used to represent lists of other JSON objects.
   TYPE_MAP = {
-    'dict':             ('TYPE_DICTIONARY',   'string',       'String'),
-    'external':         ('TYPE_EXTERNAL',     'string',       'String'),
-    'int':              ('TYPE_INTEGER',      'int64',        'Integer'),
-    'int-enum':         ('TYPE_INTEGER',      'int64',        'Integer'),
-    'list':             ('TYPE_LIST',         'StringList',   'StringList'),
-    'main':             ('TYPE_BOOLEAN',      'bool',         'Boolean'),
-    'string':           ('TYPE_STRING',       'string',       'String'),
-    'string-enum':      ('TYPE_STRING',       'string',       'String'),
-    'string-enum-list': ('TYPE_LIST',         'StringList',   'StringList'),
+    'dict':             ('TYPE_DICTIONARY',   'string',       'String',
+                        'string',             False),
+    'external':         ('TYPE_EXTERNAL',     'string',       'String',
+                        'invalid',            False),
+    'int':              ('TYPE_INTEGER',      'int64',        'Integer',
+                        'integer',            False),
+    'int-enum':         ('TYPE_INTEGER',      'int64',        'Integer',
+                        'choice',             True),
+    'list':             ('TYPE_LIST',         'StringList',   'StringList',
+                        'string',             False),
+    'main':             ('TYPE_BOOLEAN',      'bool',         'Boolean',
+                        'bool',               False),
+    'string':           ('TYPE_STRING',       'string',       'String',
+                        'string',             False),
+    'string-enum':      ('TYPE_STRING',       'string',       'String',
+                        'choice',             True),
+    'string-enum-list': ('TYPE_LIST',         'StringList',   'StringList',
+                        'multi-select',       True),
   }
 
   class EnumItem:
@@ -85,8 +97,9 @@
     if not PolicyDetails.TYPE_MAP.has_key(policy['type']):
       raise NotImplementedError('Unknown policy type for %s: %s' %
                                 (policy['name'], policy['type']))
-    self.policy_type, self.protobuf_type, self.policy_protobuf_type = \
-        PolicyDetails.TYPE_MAP[policy['type']]
+    self.policy_type, self.protobuf_type, self.policy_protobuf_type, \
+        self.restriction_type, self.has_restriction_resources = \
+            PolicyDetails.TYPE_MAP[policy['type']]
     self.schema = policy['schema']
 
     self.desc = '\n'.join(
@@ -136,6 +149,17 @@
                     dest='cloud_policy_decoder_path',
                     help='generate C++ code decoding the cloud policy protobuf',
                     metavar='FILE')
+  parser.add_option('--ard', '--app-restrictions-definition',
+                    dest='app_restrictions_path',
+                    help='generate an XML file as specified by '
+                    'Android\'s App Restriction Schema',
+                    metavar='FILE')
+  parser.add_option('--arr', '--app-restrictions-resources',
+                    dest='app_resources_path',
+                    help='generate an XML file with resources supporting the '
+                    'restrictions defined in --app-restrictions-definition '
+                    'parameter',
+                    metavar='FILE')
 
   (opts, args) = parser.parse_args()
 
@@ -153,10 +177,10 @@
                      for policy in _Flatten(template_file_contents) ]
   sorted_policy_details = sorted(policy_details, key=lambda policy: policy.name)
 
-  def GenerateFile(path, writer, sorted=False):
+  def GenerateFile(path, writer, sorted=False, xml=False):
     if path:
       with open(path, 'w') as f:
-        _OutputGeneratedWarningHeader(f, template_file_name)
+        _OutputGeneratedWarningHeader(f, template_file_name, xml)
         writer(sorted and sorted_policy_details or policy_details, os, f)
 
   GenerateFile(opts.header_path, _WritePolicyConstantHeader, sorted=True)
@@ -165,17 +189,32 @@
   GenerateFile(opts.chrome_settings_proto_path, _WriteChromeSettingsProtobuf)
   GenerateFile(opts.cloud_policy_decoder_path, _WriteCloudPolicyDecoder)
 
+  if os == 'android':
+    GenerateFile(opts.app_restrictions_path, _WriteAppRestrictions, xml=True)
+    GenerateFile(opts.app_resources_path, _WriteResourcesForPolicies, xml=True)
+
   return 0
 
 
 #------------------ shared helpers ---------------------------------#
 
-def _OutputGeneratedWarningHeader(f, template_file_path):
-  f.write('//\n'
-          '// DO NOT MODIFY THIS FILE DIRECTLY!\n'
-          '// IT IS GENERATED BY generate_policy_source.py\n'
-          '// FROM ' + template_file_path + '\n'
-          '//\n\n')
+def _OutputGeneratedWarningHeader(f, template_file_path, xml_style):
+  left_margin = '//'
+  if xml_style:
+    left_margin = '    '
+    f.write('<?xml version="1.0" encoding="utf-8"?>\n'
+            '<!--\n')
+  else:
+    f.write('//\n')
+
+  f.write(left_margin + ' DO NOT MODIFY THIS FILE DIRECTLY!\n')
+  f.write(left_margin + ' IT IS GENERATED BY generate_policy_source.py\n')
+  f.write(left_margin + ' FROM ' + template_file_path + '\n')
+
+  if xml_style:
+    f.write('-->\n\n')
+  else:
+    f.write(left_margin + '\n\n')
 
 
 COMMENT_WRAPPER = textwrap.TextWrapper()
@@ -561,7 +600,7 @@
     return self.id_map[id_str]
 
   def ResolveID(self, index, params):
-    return params[:index] + (self.GetByID(params[index]),) + params[index+1:]
+    return params[:index] + (self.GetByID(params[index]),) + params[index + 1:]
 
   def ResolveReferences(self):
     """Resolve reference mapping, required to be called after Generate()
@@ -968,5 +1007,82 @@
   f.write(CPP_FOOT)
 
 
+def _EscapeResourceString(raw_resource):
+  if type(raw_resource) == int:
+    return raw_resource
+  return xml_escape(raw_resource)\
+      .replace('\\', '\\\\')\
+      .replace('\"','\\\"')\
+      .replace('\'','\\\'')
+
+def _WriteAppRestrictions(policies, os, f):
+
+  def WriteRestrictionCommon(key):
+    f.write('    <restriction\n'
+            '        android:key="%s"\n' % key)
+    f.write('        android:title="@string/%sTitle"\n' % key)
+    f.write('        android:description="@string/%sDesc"\n' % key)
+
+  def WriteItemsDefinition(key):
+    f.write('        android:entries="@array/%sEntries"\n' % key)
+    f.write('        android:entryValues="@array/%sValues"\n' % key)
+
+  def WriteAppRestriction(policy):
+    policy_name = policy.name
+    WriteRestrictionCommon(policy_name)
+
+    if policy.has_restriction_resources:
+      WriteItemsDefinition(policy_name)
+
+    f.write('        android:restrictionType="%s"/>' % policy.restriction_type)
+    f.write('\n\n')
+
+  # _WriteAppRestrictions body
+  f.write('<restrictions xmlns:android="'
+          'http://schemas.android.com/apk/res/android">\n\n')
+  for policy in policies:
+    if policy.is_supported and policy.restriction_type != 'invalid':
+      WriteAppRestriction(policy)
+  f.write('</restrictions>')
+
+
+def _WriteResourcesForPolicies(policies, os, f):
+
+  # TODO(knn): Update this to support i18n.
+  def WriteString(key, value):
+    f.write('    <string name="%s">%s</string>\n'
+            % (key, _EscapeResourceString(value)))
+
+  def WriteItems(key, items):
+    if items:
+      f.write('    <string-array name="%sEntries">\n' % key)
+      for item in items:
+        f.write('        <item>%s</item>\n' %
+                _EscapeResourceString(item.caption))
+      f.write('    </string-array>\n')
+      f.write('    <string-array name="%sValues">\n' % key)
+      for item in items:
+        f.write('        <item>%s</item>\n' % _EscapeResourceString(item.value))
+      f.write('    </string-array>\n')
+
+  def WriteResourceForPolicy(policy):
+    policy_name = policy.name
+    WriteString(policy_name + 'Title', policy.caption)
+
+    # Get the first line of the policy description.
+    description = policy.desc.split('\n', 1)[0]
+    WriteString(policy_name + 'Desc', description)
+
+    if policy.has_restriction_resources:
+      WriteItems(policy_name, policy.items)
+
+  # _WriteResourcesForPolicies body
+  f.write('<resources>\n\n')
+  for policy in policies:
+    if policy.is_supported and policy.restriction_type != 'invalid':
+      WriteResourceForPolicy(policy)
+  f.write('</resources>')
+
+
 if __name__ == '__main__':
   sys.exit(main())